Skip to content

Lct¤

lct_sampling_quick

abcdlux_patch.lct_sampling_quick(x_in, x_out, lam, ABCD, eps=1e-30) ¤

Quick sampling diagnostics for Collins LCT on separable grids.

Returns Nyquist ratios (>=1 is safe-ish): - p_kernel: sampling of exp(-i 2π x x' /(λ b)) kernel - p_pre: sampling of input chirp exp(i π a x^2 /(λ b)) - p_post: sampling of output chirp exp(i π d x'^2 /(λ b))

Source code in abcdlux_patch.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def lct_sampling_quick(x_in, x_out, lam, ABCD, eps=1e-30):
    """
    Quick sampling diagnostics for Collins LCT on separable grids.

    Returns Nyquist ratios (>=1 is safe-ish):
      - p_kernel: sampling of exp(-i 2π x x' /(λ b)) kernel
      - p_pre:    sampling of input chirp exp(i π a x^2 /(λ b))
      - p_post:   sampling of output chirp exp(i π d x'^2 /(λ b))
    """
    a, b, c, d = [ABCD.reshape(-1)[i] for i in range(4)]

    dx_in = x_in[1] - x_in[0]
    dx_out = x_out[1] - x_out[0]
    X_in = 0.5 * (x_in[-1] - x_in[0])
    X_out = 0.5 * (x_out[-1] - x_out[0])

    # Kernel phase: exp(-i 2π x x' /(λ b))
    # worst phase slope in x is at max |x'|
    dphi_in_max = (2 * jnp.pi / (lam * jnp.abs(b) + eps)) * X_out * dx_in
    dphi_out_max = (2 * jnp.pi / (lam * jnp.abs(b) + eps)) * X_in * dx_out
    p_kernel = jnp.pi / (jnp.maximum(dphi_in_max, dphi_out_max) + eps)

    # Pre/post chirps: phase ~ π a x^2 /(λ b), slope ~ 2π a x /(λ b)
    dphi_pre_max = (2 * jnp.pi * jnp.abs(a) / (lam * jnp.abs(b) + eps)) * X_in * dx_in
    dphi_post_max = (
        (2 * jnp.pi * jnp.abs(d) / (lam * jnp.abs(b) + eps)) * X_out * dx_out
    )
    p_pre = jnp.pi / (dphi_pre_max + eps)
    p_post = jnp.pi / (dphi_post_max + eps)

    return {"p_kernel": p_kernel, "p_pre": p_pre, "p_post": p_post}
lct_kernels

abcdlux_patch.lct_kernels(spec_in, spec_out, lam, ABCD) ¤

Source code in abcdlux_patch.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
def lct_kernels(
    spec_in: Array | tuple, spec_out: Array | tuple, lam: float, ABCD: Array
) -> tuple:
    x_in, y_in = unpack_coord_spec(spec_in)
    x_out, y_out = unpack_coord_spec(spec_out)

    a, b, c, d = ABCD.flatten()

    r2_in = r2_coords((x_in, y_in))
    r2_out = r2_coords((x_out, y_out))

    pre = np.exp(1j * np.pi * a * r2_in / (lam * b))
    post = np.exp(1j * np.pi * d * r2_out / (lam * b))

    dx_in = x_in[1] - x_in[0]
    dy_in = y_in[1] - y_in[0]
    dx_out = x_out[1] - x_out[0]
    dy_out = y_out[1] - y_out[0]

    alpha = -2.0 * np.pi / (lam * b)

    Kx, Ky = mft_kernels(
        spec_in=spec_in, spec_out=spec_out, alpha=alpha, weight=(dx_in, dy_in)
    )

    pref = 1.0 / (1j * lam * b)
    scale = np.sqrt((dx_out * dy_out) / (dx_in * dy_in))
    return pre, Kx, Ky, post, pref, scale
lct_kernel_prop

abcdlux_patch.lct_kernel_prop(u_in, pre, Kx, Ky, post, pref, scale) ¤

Source code in abcdlux_patch.py
324
325
326
327
328
329
330
331
332
333
334
335
def lct_kernel_prop(
    u_in: Array,
    pre: Array,
    Kx: Array,
    Ky: Array,
    post: Array,
    pref: complex,
    scale: float,
) -> Array:
    u_tmp = pre * u_in
    u_mft = mft(u_tmp, Kx, Ky)
    return pref * scale * u_mft * post
lct_prop_basic

abcdlux_patch.lct_prop_basic(u_in, spec_in, spec_out, lam, ABCD) ¤

Source code in abcdlux_patch.py
338
339
340
341
342
343
344
345
346
def lct_prop_basic(
    u_in: Array,
    spec_in: Array | tuple,
    spec_out: Array | tuple,
    lam: float,
    ABCD: Array,
) -> Array:
    pre, Kx, Ky, post, pref, scale = lct_kernels(spec_in, spec_out, lam, ABCD)
    return lct_kernel_prop(u_in, pre, Kx, Ky, post, pref, scale)
lct_prop

abcdlux_patch.lct_prop(u_in, spec_in, spec_out, lam, ABCD, curv_in=None, curv_out=None, mode='physical', strip_input=True, return_residual=False) ¤

Source code in abcdlux_patch.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def lct_prop(
    u_in: Array,
    spec_in: Array | tuple,
    spec_out: Array | tuple,
    lam: float,
    ABCD: Array,
    curv_in: float | None = None,
    curv_out: float | None = None,
    mode: str = "physical",
    strip_input: bool = True,
    return_residual: bool = False,
) -> Array:
    ABCD_res, curv_in, curv_out = factorise_curv(ABCD, curv_in, curv_out, mode)

    u_res_in = remove_curv(u_in, spec_in, lam, curv_in)

    u_res_out = lct_prop_basic(u_res_in, spec_in, spec_out, lam, ABCD_res)

    if return_residual:
        return u_res_out

    return apply_curv(u_res_out, spec_out, lam, curv_out)
propagate_mono_abcd

abcdlux_patch.propagate_mono_abcd(self, wavelength, offset=np.zeros(2), return_wf=False) ¤

Source code in abcdlux_patch.py
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
def propagate_mono_abcd(self, wavelength, offset=np.zeros(2), return_wf=False):
    import dLux as dl  # keep local

    wf = dl.Wavefront(self.wf_npixels, self.diameter, wavelength).tilt(offset)

    for layer in list(self.layers.values()):
        wf *= layer

    u_in = wf.phasor

    fl = self.fnumber * self.diameter  # meters

    # If your existing code uses self.defocus in nm, convert:
    z_defocus = self.defocus * 1e-9

    abcd = compose_abcd([abcd_lens(fl), abcd_free_space(fl + z_defocus)])

    # Input sampling (pupil plane)
    N_in = self.wf_npixels
    dx_in = self.diameter / self.wf_npixels
    x_in = dlu.nd_coords(N_in, dx_in)

    # Output sampling: match your existing psf_pixel_scale (angular) via x = f * theta
    true_pixel_scale = self.psf_pixel_scale / self.oversample  # arcsec/pix
    theta_pix = dlu.arcsec2rad(true_pixel_scale)  # rad/pix

    N_out = self.psf_npixels * self.oversample
    dx_out = fl * theta_pix  # meters/pix at focal plane

    x_out = dlu.nd_coords(N_out, dx_out)

    u_out = lct_prop_basic(u_in, x_in, x_out, wavelength, abcd)

    wf_out = dl.Wavefront(N_out, N_out * dx_out, wavelength).set(
        ["amplitude", "phase"], [np.abs(u_out), np.angle(u_out)]
    )

    if return_wf:
        return wf_out
    return wf_out.psf