Skip to content

Commit 6dae0d0

Browse files
committed
feat: add keops algorithm
1 parent 0dc4918 commit 6dae0d0

2 files changed

Lines changed: 124 additions & 10 deletions

File tree

src/pyGroupedTransforms/GroupedTransform.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def __init__(
183183
system,
184184
X,
185185
settings=[],
186-
fastmult=True,
186+
algorithm="nfft",
187187
parallel=True,
188188
basis_vect=[],
189189
N=[],
@@ -227,9 +227,9 @@ def __init__(
227227
)
228228

229229
if system in {"chui1", "chui2", "chui3", "chui4"}:
230-
fastmult = True
230+
algorithm = "nfft"
231231

232-
self.fastmult = fastmult
232+
self.algorithm = algorithm
233233
self.basis_vect = basis_vect
234234
self.system = system
235235
self.X = X
@@ -240,7 +240,7 @@ def __init__(
240240
else:
241241
self.settings = settings
242242

243-
if fastmult:
243+
if algorithm == "nfft":
244244
self.matrix = np.empty((0, 0), dtype=object)
245245
self.transforms = [
246246
DeferredLinearOperator() for i in range(len(self.settings))
@@ -265,6 +265,25 @@ def __init__(
265265
self.transforms[idx] = s.mode.get_transform(
266266
bandwidths=s.bandwidths, X=np.copy(X[:, u], order="C")
267267
)
268+
elif algorithm == "keops":
269+
self.matrix = np.empty((0,0), dtype=object)
270+
271+
self.transforms = [
272+
DeferredLinearOperator()
273+
for _ in range(len(self.settings))
274+
]
275+
276+
for idx, s in enumerate(self.settings):
277+
278+
if len(s.bandwidths) == 0:
279+
u = (0,)
280+
else:
281+
u = s.u
282+
283+
self.transforms[idx] = s.mode.get_transform_keops(
284+
bandwidths=s.bandwidths,
285+
X=np.copy(X[:,u], order="C")
286+
)
268287
else:
269288
self.transforms = []
270289
s1 = self.settings[0]
@@ -321,9 +340,8 @@ def __mul__(self, other):
321340
If other (= f) is an numpy.ndarray, this function
322341
overloads the * notation in order to achieve the adjoint transform `f = F*f`.
323342
"""
324-
325343
if isinstance(other, np.ndarray): # `f = F*f` (f = other)
326-
if self.fastmult:
344+
if self.algorithm == "nfft":
327345
fhat = GroupedCoefficients(self.settings)
328346

329347
def adjoint_worker(i):
@@ -345,17 +363,26 @@ def adjoint_worker(i):
345363
adjoint_worker(i)
346364

347365
return fhat
348-
else:
366+
elif self.algorithm == "keops":
367+
fhat = GroupedCoefficients(self.settings)
368+
369+
for i in range(len(self.transforms)):
370+
adjoint_result = self.transforms[i].H @ other
371+
fhat[self.settings[i].u] = adjoint_result
372+
373+
return fhat
374+
elif self.algorithm == "direct":
349375
return GroupedCoefficients(
350376
self.settings, (self.matrix.conj()).T @ other
351377
)
378+
352379
elif isinstance(other, GC): # `f = F*fhat` (fhat = other)
353380
if self.settings != other.settings:
354381
raise ValueError(
355382
"The GroupedTransform and the GroupedCoefficients have different settings"
356383
)
357384

358-
if self.fastmult:
385+
if self.algorithm == "nfft":
359386
results = []
360387

361388
def worker(i):
@@ -376,7 +403,16 @@ def worker(i):
376403
worker(i)
377404

378405
return sum(results)
379-
else:
406+
elif self.algorithm == "keops":
407+
results = []
408+
409+
for i in range(len(self.transforms)):
410+
u = self.settings[i].u
411+
result = self.transforms[i] @ other[u]
412+
results.append(result)
413+
414+
return sum(results)
415+
elif self.algorithm == "direct":
380416
return self.matrix @ other.data
381417
else:
382418
raise ValueError("Wrong input data type")
@@ -412,7 +448,7 @@ def __getitem__(self, u):
412448

413449
if idx is None:
414450
raise ValueError("This term is not contained")
415-
elif self.fastmult:
451+
elif self.algorithm == "nfft":
416452
return self.transforms[idx]
417453
else:
418454
return self.get_matrix()

src/pyGroupedTransforms/NFFTtools.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
from pyGroupedTransforms import *
44

5+
import torch
6+
import pykeops
7+
from pykeops.torch import LazyTensor
8+
59

610
def datalength(
711
bandwidths: np.ndarray,
@@ -168,6 +172,80 @@ def adjoint(f): # function adjoint(f::Vector{ComplexF64})::Vector{ComplexF64}
168172
return DeferredLinearOperator(
169173
dtype=np.complex128, shape=(M, N), mfunc=trafo, rmfunc=adjoint
170174
)
175+
176+
def get_transform_keops(
177+
bandwidths: np.array, X: np.array
178+
):
179+
if bandwidths.ndim > 1 or bandwidths.dtype != "int32":
180+
return "Please use an zero or one-dimensional numpy.array with dtype 'int32' as input"
181+
182+
M, d = np.shape(X)
183+
184+
if len(bandwidths) == 0:
185+
return DeferredLinearOperator(
186+
dtype=np.complex128,
187+
shape=(M, 1),
188+
mfunc=lambda fhat: np.full(M, fhat[0]),
189+
rmfunc=lambda f: np.array([np.sum(f)]),
190+
)
191+
192+
X_torch = torch.tensor(X, dtype=torch.float64, device="cuda")
193+
freq = index_set_without_zeros(
194+
np.array(bandwidths, dtype=np.int32)
195+
).T
196+
197+
I_torch = torch.tensor(
198+
freq,
199+
dtype=torch.float64,
200+
device="cuda"
201+
)
202+
203+
204+
def trafo(fhat):
205+
X_i = LazyTensor(X_torch[:, None, :].contiguous())
206+
K_j = LazyTensor(I_torch[None, :, :].contiguous())
207+
phase_fwd = (X_i * K_j).sum(-1)
208+
two_pi_phase = -2 * torch.pi * phase_fwd
209+
kernel_fwd = two_pi_phase.cos() + 1j * two_pi_phase.sin()
210+
fhat_torch = torch.tensor(fhat, dtype=torch.complex128, device="cuda")
211+
fhat_j = LazyTensor(fhat_torch[None, :, None].contiguous())
212+
213+
try:
214+
result = (kernel_fwd * fhat_j).sum(dim=1)
215+
torch.cuda.synchronize()
216+
result = result.squeeze().cpu().numpy()
217+
return result
218+
except Exception as e:
219+
print("Error in KeOps trafo:", e)
220+
return None
221+
222+
def adjoint(f):
223+
f_torch = torch.tensor(f, dtype=torch.complex128, device="cuda").contiguous()
224+
225+
X_i = LazyTensor(X_torch[None, :, :].contiguous())
226+
K_j = LazyTensor(I_torch[:, None, :].contiguous())
227+
228+
phase = (K_j * X_i).sum(-1)
229+
kernel = (-2*torch.pi*phase).cos() - 1j*(-2*torch.pi*phase).sin()
230+
231+
f_i = LazyTensor(f_torch[None, :, None].contiguous())
232+
233+
try:
234+
result = (kernel * f_i).sum(dim=1)
235+
torch.cuda.synchronize()
236+
result = result.squeeze().cpu().numpy()
237+
238+
return result
239+
except Exception as e:
240+
print("Error in KeOps adjoint:", e)
241+
return None
242+
243+
244+
Ncoeffs = np.prod(bandwidths - 1)
245+
246+
return DeferredLinearOperator(
247+
dtype=np.complex128, shape=(M, Ncoeffs), mfunc=trafo, rmfunc=adjoint
248+
)
171249

172250

173251
def get_matrix(

0 commit comments

Comments
 (0)