faster-diffbloch 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.
@@ -0,0 +1,233 @@
1
+ #include "native_scattering.h"
2
+ #include <math.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include <dispatch/dispatch.h>
6
+
7
+ #define PI 3.14159265358979323846
8
+ #define TWO_PI 6.283185307179586
9
+ #define MINUS_TWO_PI_SQ -19.739208802178716
10
+
11
+ static inline double native_lobato(const double* coeff_a, const double* coeff_b, int32_t base, double g2) {
12
+ double f = 0.0;
13
+ for (int i = 0; i < 5; i++) {
14
+ double b = coeff_b[base + i];
15
+ double denom = 1.0 + b * g2;
16
+ f += coeff_a[base + i] * (2.0 + b * g2) / (denom * denom);
17
+ }
18
+ return f;
19
+ }
20
+
21
+ static inline double native_dwf_exponent(const double* uij, int32_t base, double h0, double h1, double h2) {
22
+ double quad = h0 * (uij[base] * h0 + uij[base + 1] * h1 + uij[base + 2] * h2)
23
+ + h1 * (uij[base + 3] * h0 + uij[base + 4] * h1 + uij[base + 5] * h2)
24
+ + h2 * (uij[base + 6] * h0 + uij[base + 7] * h1 + uij[base + 8] * h2);
25
+ return MINUS_TWO_PI_SQ * quad;
26
+ }
27
+
28
+ typedef struct {
29
+ double value;
30
+ double derivative;
31
+ } AbsorptiveResult;
32
+
33
+ static inline AbsorptiveResult native_absorptive_factor(
34
+ const NativeCase* c, int32_t atom, int32_t slot, double b, int clamped
35
+ ) {
36
+ double width = c->abs_knot_width[atom];
37
+ double t = (b - c->abs_knot_lo[atom]) / width;
38
+ double t2 = t * t;
39
+ double t3 = t2 * t;
40
+ double raw = (2.0 * t3 - 3.0 * t2 + 1.0) * c->abs_y0[slot]
41
+ + (t3 - 2.0 * t2 + t) * width * c->abs_d0[slot]
42
+ + (-2.0 * t3 + 3.0 * t2) * c->abs_y1[slot]
43
+ + (t3 - t2) * width * c->abs_d1[slot];
44
+ if (raw <= 0.0) {
45
+ return (AbsorptiveResult){ 0.0, 0.0 };
46
+ }
47
+ if (clamped) {
48
+ return (AbsorptiveResult){ c->abs_c_over_v * raw, 0.0 };
49
+ }
50
+ double slope = (6.0 * t2 - 6.0 * t) * c->abs_y0[slot]
51
+ + (3.0 * t2 - 4.0 * t + 1.0) * width * c->abs_d0[slot]
52
+ + (-6.0 * t2 + 6.0 * t) * c->abs_y1[slot]
53
+ + (3.0 * t2 - 2.0 * t) * width * c->abs_d1[slot];
54
+ return (AbsorptiveResult){
55
+ c->abs_c_over_v * raw,
56
+ c->abs_c_over_v * slope / width
57
+ };
58
+ }
59
+
60
+ void native_parallel_structure_factors(
61
+ const NativeCase* c,
62
+ double _Complex* fgb,
63
+ NativePhaseCache* cache
64
+ ) {
65
+ int32_t n_atoms = c->n_atoms;
66
+ int32_t n_grid = c->n_grid;
67
+
68
+ double* b_iso = (double*)malloc(n_atoms * sizeof(double));
69
+ int32_t* clamped = (int32_t*)malloc(n_atoms * sizeof(int32_t));
70
+
71
+ for (int a = 0; a < n_atoms; a++) {
72
+ clamped[a] = 0;
73
+ if (c->absorption == 1) {
74
+ int32_t base = a * 9;
75
+ double b = 0.0;
76
+ for (int i = 0; i < 9; i++) {
77
+ b += c->abs_db_du[i] * c->uij[base + i];
78
+ }
79
+ if (b < 0.1) { b = 0.1; clamped[a] = 1; }
80
+ if (b > 4.0) { b = 4.0; clamped[a] = 1; }
81
+ b_iso[a] = b;
82
+ } else {
83
+ b_iso[a] = 0.0;
84
+ }
85
+ }
86
+
87
+ size_t num_chunks = 32;
88
+ if (n_grid < 100) num_chunks = 1;
89
+
90
+ dispatch_apply(num_chunks, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t chunk) {
91
+ int32_t start_m = (int32_t)(chunk * n_grid / num_chunks);
92
+ int32_t end_m = (int32_t)((chunk + 1) * n_grid / num_chunks);
93
+
94
+ for (int32_t m = start_m; m < end_m; m++) {
95
+ double g2 = c->grid_g[m] * c->grid_g[m];
96
+ double h0 = (double)c->grid_hkl[m * 3];
97
+ double h1 = (double)c->grid_hkl[m * 3 + 1];
98
+ double h2 = (double)c->grid_hkl[m * 3 + 2];
99
+ double re = 0.0;
100
+ double im = 0.0;
101
+
102
+ for (int a = 0; a < n_atoms; a++) {
103
+ double form = native_lobato(c->lobato_a, c->lobato_b, a * 5, g2);
104
+ double dwf = exp(native_dwf_exponent(c->uij, a * 9, h0, h1, h2));
105
+ double amplitude = form * dwf / c->volume;
106
+ double phase = TWO_PI * (c->positions[a * 3] * h0
107
+ + c->positions[a * 3 + 1] * h1
108
+ + c->positions[a * 3 + 2] * h2);
109
+ double cp = cos(phase);
110
+ double sp = sin(phase);
111
+ int32_t slot = m * n_atoms + a;
112
+
113
+ double imag_amplitude = 0.0;
114
+ double dimag_db = 0.0;
115
+ if (c->absorption == 1) {
116
+ int32_t abs_slot = a * n_grid + m;
117
+ AbsorptiveResult fp = native_absorptive_factor(c, a, abs_slot, b_iso[a], clamped[a]);
118
+ imag_amplitude = fp.value * dwf / c->volume;
119
+ dimag_db = fp.derivative * dwf / c->volume;
120
+ }
121
+
122
+ cache->amplitude[slot] = amplitude;
123
+ cache->cosine[slot] = cp;
124
+ cache->sine[slot] = sp;
125
+ cache->imag_amplitude[slot] = imag_amplitude;
126
+ cache->dimag_db[slot] = dimag_db;
127
+
128
+ double occ = c->occupancies[a];
129
+ re += occ * (amplitude * cp - imag_amplitude * sp);
130
+ im += occ * (amplitude * sp + imag_amplitude * cp);
131
+ }
132
+ fgb[m] = re + I * im;
133
+ }
134
+ });
135
+
136
+ free(b_iso);
137
+ free(clamped);
138
+ }
139
+
140
+ void native_parallel_structure_factors_backward(
141
+ const NativeCase* c,
142
+ const NativePhaseCache* cache,
143
+ const double _Complex* fbar,
144
+ double* grad_positions,
145
+ double* grad_occupancies,
146
+ double* grad_uij
147
+ ) {
148
+ int32_t n_atoms = c->n_atoms;
149
+ int32_t n_grid = c->n_grid;
150
+
151
+ for (int i = 0; i < n_atoms * 3; i++) grad_positions[i] = 0.0;
152
+ for (int i = 0; i < n_atoms; i++) grad_occupancies[i] = 0.0;
153
+ for (int i = 0; i < n_atoms * 9; i++) grad_uij[i] = 0.0;
154
+
155
+ size_t num_chunks = 16;
156
+ if (n_grid < 100) num_chunks = 1;
157
+
158
+ double* local_gpos = (double*)calloc(num_chunks * n_atoms * 3, sizeof(double));
159
+ double* local_gocc = (double*)calloc(num_chunks * n_atoms, sizeof(double));
160
+ double* local_guij = (double*)calloc(num_chunks * n_atoms * 9, sizeof(double));
161
+
162
+ dispatch_apply(num_chunks, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t chunk) {
163
+ int32_t start_m = (int32_t)(chunk * n_grid / num_chunks);
164
+ int32_t end_m = (int32_t)((chunk + 1) * n_grid / num_chunks);
165
+
166
+ double* t_gpos = local_gpos + chunk * n_atoms * 3;
167
+ double* t_gocc = local_gocc + chunk * n_atoms;
168
+ double* t_guij = local_guij + chunk * n_atoms * 9;
169
+
170
+ for (int32_t m = start_m; m < end_m; m++) {
171
+ double h0 = (double)c->grid_hkl[m * 3];
172
+ double h1 = (double)c->grid_hkl[m * 3 + 1];
173
+ double h2 = (double)c->grid_hkl[m * 3 + 2];
174
+ double bar_re = creal(fbar[m]);
175
+ double bar_im = cimag(fbar[m]);
176
+
177
+ for (int a = 0; a < n_atoms; a++) {
178
+ int32_t slot = m * n_atoms + a;
179
+ double amplitude = cache->amplitude[slot];
180
+ double imag_amplitude = cache->imag_amplitude[slot];
181
+ double cp = cache->cosine[slot];
182
+ double sp = cache->sine[slot];
183
+ double occ = c->occupancies[a];
184
+
185
+ double along = bar_re * cp + bar_im * sp;
186
+ double across = bar_im * cp - bar_re * sp;
187
+
188
+ double projected = amplitude * along + imag_amplitude * across;
189
+ t_gocc[a] += projected;
190
+
191
+ double rotated = occ * (amplitude * across - imag_amplitude * along) * TWO_PI;
192
+ t_gpos[a * 3] += rotated * h0;
193
+ t_gpos[a * 3 + 1] += rotated * h1;
194
+ t_gpos[a * 3 + 2] += rotated * h2;
195
+
196
+ double scaled = MINUS_TWO_PI_SQ * occ * projected;
197
+ int32_t base = a * 9;
198
+ if (c->absorption == 1) {
199
+ double through_b = occ * cache->dimag_db[slot] * across;
200
+ for (int i = 0; i < 9; i++) {
201
+ t_guij[base + i] += through_b * c->abs_db_du[i];
202
+ }
203
+ }
204
+ double xy = scaled * h0 * h1;
205
+ double xz = scaled * h0 * h2;
206
+ double yz = scaled * h1 * h2;
207
+ t_guij[base] += scaled * h0 * h0;
208
+ t_guij[base + 1] += xy;
209
+ t_guij[base + 2] += xz;
210
+ t_guij[base + 3] += xy;
211
+ t_guij[base + 4] += scaled * h1 * h1;
212
+ t_guij[base + 5] += yz;
213
+ t_guij[base + 6] += xz;
214
+ t_guij[base + 7] += yz;
215
+ t_guij[base + 8] += scaled * h2 * h2;
216
+ }
217
+ }
218
+ });
219
+
220
+ // Reduce thread-local accumulators
221
+ for (size_t chunk = 0; chunk < num_chunks; chunk++) {
222
+ const double* t_gpos = local_gpos + chunk * n_atoms * 3;
223
+ const double* t_gocc = local_gocc + chunk * n_atoms;
224
+ const double* t_guij = local_guij + chunk * n_atoms * 9;
225
+ for (int i = 0; i < n_atoms * 3; i++) grad_positions[i] += t_gpos[i];
226
+ for (int i = 0; i < n_atoms; i++) grad_occupancies[i] += t_gocc[i];
227
+ for (int i = 0; i < n_atoms * 9; i++) grad_uij[i] += t_guij[i];
228
+ }
229
+
230
+ free(local_gpos);
231
+ free(local_gocc);
232
+ free(local_guij);
233
+ }
@@ -0,0 +1,60 @@
1
+ #ifndef NATIVE_SCATTERING_H
2
+ #define NATIVE_SCATTERING_H
3
+
4
+ #include <stdint.h>
5
+ #include <complex.h>
6
+
7
+ #ifdef __cplusplus
8
+ extern "C" {
9
+ #endif
10
+
11
+ typedef struct {
12
+ int32_t n_atoms;
13
+ int32_t n_grid;
14
+ int32_t absorption;
15
+ double volume;
16
+ double abs_c_over_v;
17
+ const double* positions; /* n_atoms * 3 */
18
+ const double* occupancies; /* n_atoms */
19
+ const double* uij; /* n_atoms * 9 */
20
+ const double* lobato_a; /* n_atoms * 5 */
21
+ const double* lobato_b; /* n_atoms * 5 */
22
+ const int32_t* grid_hkl; /* n_grid * 3 */
23
+ const double* grid_g; /* n_grid */
24
+ const double* abs_db_du; /* 9 */
25
+ const double* abs_knot_lo; /* n_atoms */
26
+ const double* abs_knot_width;/* n_atoms */
27
+ const double* abs_y0; /* n_atoms * n_grid */
28
+ const double* abs_y1;
29
+ const double* abs_d0;
30
+ const double* abs_d1;
31
+ } NativeCase;
32
+
33
+ typedef struct {
34
+ double* amplitude; /* n_grid * n_atoms */
35
+ double* cosine;
36
+ double* sine;
37
+ double* imag_amplitude;
38
+ double* dimag_db;
39
+ } NativePhaseCache;
40
+
41
+ void native_parallel_structure_factors(
42
+ const NativeCase* c,
43
+ double _Complex* fgb,
44
+ NativePhaseCache* cache
45
+ );
46
+
47
+ void native_parallel_structure_factors_backward(
48
+ const NativeCase* c,
49
+ const NativePhaseCache* cache,
50
+ const double _Complex* fbar,
51
+ double* grad_positions,
52
+ double* grad_occupancies,
53
+ double* grad_uij
54
+ );
55
+
56
+ #ifdef __cplusplus
57
+ }
58
+ #endif
59
+
60
+ #endif /* NATIVE_SCATTERING_H */
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.5
2
+ Name: faster-diffbloch
3
+ Version: 0.1.0
4
+ Summary: Drop-in Metal GPU and CPU acceleration for diffBloch electron crystallography
5
+ Project-URL: Homepage, https://godofecht.github.io/diffFlow/
6
+ Project-URL: Repository, https://github.com/godofecht/diffFlow
7
+ Project-URL: Issues, https://github.com/godofecht/diffFlow/issues
8
+ Project-URL: Original diffBloch, https://diffbloch.com
9
+ Author-email: Abhishek Shivakumar <abhishek@example.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Physics
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: numpy>=1.24
22
+ Requires-Dist: torch>=2.0
23
+ Provides-Extra: diffbloch
24
+ Requires-Dist: diffbloch; extra == 'diffbloch'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # faster-diffBloch
28
+
29
+ Drop-in Apple Silicon Metal GPU and optimized CPU acceleration for [diffBloch](https://diffbloch.com) electron crystallography structure refinement.
30
+
31
+ Documentation and comparison benchmarks: [https://godofecht.github.io/diffFlow/](https://godofecht.github.io/diffFlow/)
32
+
33
+ Original diffBloch project: [https://diffbloch.com](https://diffbloch.com)
34
+
35
+ ---
36
+
37
+ ## Why faster-diffBloch?
38
+
39
+ 1. **Native Metal GPU Execution:**
40
+ PyTorch MPS lacks a native GPU kernel for `aten::linalg_matrix_exp`, which causes PyTorch to fall back to CPU execution with host-device memory transfers. `faster-diffBloch` executes matrix exponentials directly on Apple Silicon Metal with zero-copy unified memory.
41
+
42
+ 2. **Blocked-Pair Adjoint Formulation:**
43
+ Standard matrix exponential autograd embeds the operator into a $2N \times 2N$ block matrix, costing $8 \times N^3$ FLOPs. `faster-diffBloch` evaluates the pullback in the block-triangular pair algebra $(Y_a Y_b, Y_a L_b + L_a Y_b)$, reducing the work to $3 \times N^3$ FLOPs (2.67x fewer products).
44
+
45
+ 3. **Bit-for-Bit Validation:**
46
+ Passes all 738 unit tests in diffBloch and reproduces the experimental 99-rotation quartz dataset ($R_{\text{obs}} = 0.0486$).
47
+
48
+ ---
49
+
50
+ ## Performance
51
+
52
+ Forward and backward timing comparison on Apple Silicon (M4 Max) at $N=579$ beams (CsPbBr3 scale):
53
+
54
+ | Implementation | Forward | Forward + Backward | Speedup vs PyTorch CPU | Speedup vs PyTorch MPS |
55
+ | :--- | :---: | :---: | :---: | :---: |
56
+ | PyTorch CPU | 25.7 ms | 130.3 ms | 1.00x | 1.17x |
57
+ | PyTorch MPS (fallback) | 26.2 ms | 153.0 ms | 0.85x | 1.00x |
58
+ | **faster-diffBloch CPU** | **24.4 ms** | **83.5 ms** | **1.56x** | **1.83x** |
59
+ | **faster-diffBloch Metal GPU** | **13.1 ms** | **58.1 ms** | **2.24x** | **2.63x** |
60
+
61
+ ---
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ pip install faster-diffbloch
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Usage
72
+
73
+ ### 1. Drop-in CLI
74
+
75
+ Use `diffbloch-fast` or `faster-diffbloch` anywhere you would use `diffbloch`:
76
+
77
+ ```bash
78
+ diffbloch-fast infer examples/Colmey_et_al_2026/data/quartz-no-abs
79
+ diffbloch-fast refine examples/Colmey_et_al_2026/data/quartz-no-abs
80
+ ```
81
+
82
+ ### 2. Python API Injection
83
+
84
+ Enable acceleration inside any existing diffBloch script:
85
+
86
+ ```python
87
+ import faster_diffbloch
88
+
89
+ # Enable Metal GPU acceleration
90
+ faster_diffbloch.enable(device="gpu")
91
+
92
+ # Or CPU acceleration
93
+ faster_diffbloch.enable(device="cpu")
94
+
95
+ # Run standard diffBloch code
96
+ import diffBloch
97
+ # All propagate and matrix_exp calls now route through faster-diffBloch
98
+ ```
@@ -0,0 +1,16 @@
1
+ faster_diffbloch/__init__.py,sha256=XaxuW-wZ-zyzryZ6vdOZbabHWfBPdDfn41oTmhLmvsg,330
2
+ faster_diffbloch/backend.py,sha256=iYGZ-Y7KEL6d9S0JSgcRkUHK2akFFW5s2PTbbKYmjyk,5866
3
+ faster_diffbloch/builder.py,sha256=D2CWVSUHWVcj0BEV5KOavuKeXA22-RTKgImSo-nAIc0,2447
4
+ faster_diffbloch/cli.py,sha256=kwX6W-vfRhCRS_VvLRzor-3XPkFXfgrJUYwsu84hGl0,547
5
+ faster_diffbloch/native/batch_cgemm.c,sha256=VX_hr9oauetvtna-MbJJ4y3yIs5ct-bzLpq4Rn9nrP0,5935
6
+ faster_diffbloch/native/batch_cgemm.h,sha256=9BOPBfAyA87sGBs3mS0oe8nfrjz4oHim2hTPnoq3P7E,3135
7
+ faster_diffbloch/native/batch_cgemm.metal,sha256=A0UPhvY3ve-ITGY9CQBVxDOD59TzaIZBG6ABgjNr03I,11937
8
+ faster_diffbloch/native/bridge_lib.c,sha256=v-tOdgzL78VuGWdruyKcTEbRqxVfDivK_gaUG5PjSpY,31072
9
+ faster_diffbloch/native/metal_batch_cgemm.m,sha256=nQqewJ3OuYePLcglKf63qsD1Jqzsaa7rtZv6hvYEF28,34129
10
+ faster_diffbloch/native/native_scattering.c,sha256=M6nCLothcCNG0BMLTCFAb3w9d8RZWQzgUpBIVsouBZg,9032
11
+ faster_diffbloch/native/native_scattering.h,sha256=ynMJpbptbZZwRcJvo-azzDje9Cs5x1m9xsBbUkmWm5c,1488
12
+ faster_diffbloch-0.1.0.dist-info/METADATA,sha256=1lR3t7kNdwiHMShutr7h-eKLqMgxc12QgJv-RkK-XM4,3549
13
+ faster_diffbloch-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
14
+ faster_diffbloch-0.1.0.dist-info/entry_points.txt,sha256=pPjLbGQUv0VMhCee6wfeBgMPKVgdc32ycSjmjaDx82s,106
15
+ faster_diffbloch-0.1.0.dist-info/licenses/LICENSE,sha256=Fhvu-xKhUMhKf6kCYY_xMnvH7vMPU6y6kggs07icYMs,1076
16
+ faster_diffbloch-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ diffbloch-fast = faster_diffbloch.cli:main
3
+ faster-diffbloch = faster_diffbloch.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abhishek Shivakumar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.