Skip to content

Configuration

vectorwaves.config_stuff.Config dataclass

Bases: SerializableConfig

Main VectorWaves Configuration.

This is the root tree for initializing an experiment. It manages the global backend, the observation plane settings, and the complex light source properties.

Example: config = get_config() config.op.size = (20.0, 20.0) config.source.k_space.gaussian(sigma_k_perp=1.0) config.source.randomize.off()

Attributes:

Name Type Description
backend Literal['auto', 'numpy', 'numba', 'cupy', 'cupy64']

Computational backend to execute evaluations. Defaults to "auto". All backends perform exactly the same physical superposition of plane waves, but scale differently based on hardware: - "numpy" : Vectorized CPU fallback (highest RAM usage, best for small sets). - "numba" : JIT compiled parallel CPU loops (low RAM usage, fast CPU). - "cupy32" : Single-precision GPU execution (fastest for massive computations). - "cupy64" : Double-precision GPU execution (slower than cupy, but exact). - "auto" : Defaults to cupy64 if available, else numba, else numpy.

op OpConfig

Settings for the Observation Plane (grid size, center, resolution).

source SourceConfig

Settings for the Light Source (beam axis, wavelength, spatial/spectral profiles).

verbose bool

If True, prints execution details and deep warnings.

Source code in src\vectorwaves\config_stuff.py
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
@dataclass(slots=True)
class Config(SerializableConfig):
    """
    Main VectorWaves Configuration.

    This is the root tree for initializing an experiment. It manages the global 
    backend, the observation plane settings, and the complex light source properties.

    Example:
        `config = get_config()`
        `config.op.size = (20.0, 20.0)`
        `config.source.k_space.gaussian(sigma_k_perp=1.0)`
        `config.source.randomize.off()`

    Attributes
    ----------
    backend : Literal["auto", "numpy", "numba", "cupy", "cupy64"]
        Computational backend to execute evaluations. Defaults to "auto".
        All backends perform exactly the same physical superposition of plane waves, 
        but scale differently based on hardware:
        - "numpy"  : Vectorized CPU fallback (highest RAM usage, best for small sets).
        - "numba"  : JIT compiled parallel CPU loops (low RAM usage, fast CPU).
        - "cupy32" : Single-precision GPU execution (fastest for massive computations).
        - "cupy64" : Double-precision GPU execution (slower than cupy, but exact).
        - "auto"   : Defaults to cupy64 if available, else numba, else numpy.
    op : OpConfig
        Settings for the Observation Plane (grid size, center, resolution).
    source : SourceConfig
        Settings for the Light Source (beam axis, wavelength, spatial/spectral profiles).
    verbose : bool
        If True, prints execution details and deep warnings.
    """
    backend: Literal["auto", "numpy", "numba", "cupy32", "cupy64"] = "auto"
    op: OpConfig = field(default_factory=OpConfig)
    source: SourceConfig = field(default_factory=SourceConfig)
    verbose: bool = False

    def validate(self):
        self.op.validate()
        self.source.validate()

        self.backend = str(self.backend).lower()
        allowed = ["numba", "numpy", "auto", "cupy32", "cupy64"]
        if self.backend not in allowed:
            raise ValueError(f"Invalid backend: {self.backend}. Must be one of {allowed}")

        wls = np.atleast_1d(self.source.wavelength)
        min_wl = np.min(wls)
        if self.op.spacing > min_wl / 2:
            warnings.warn(
                f"Spatial aliasing detected. Grid spacing ({self.op.spacing}) "
                f"is larger than half the minimum wavelength ({min_wl/2:.4f}).", 
                UserWarning
            )

    def __post_init__(self):
        self.validate()

vectorwaves.config_stuff.SourceConfig dataclass

Bases: SerializableConfig

Light Source Configuration.

Aggregates all physical parameters defining the initial state of the light field, including nested properties like spatial modes and polarization. Access this via config.source.

Example: config.source.intensity_scale = 1e3 config.source.wavelength = 0.532 config.source.k_space.gaussian(sigma_k_perp=2.0) config.source.randomize.off()

Attributes:

Name Type Description
k_space KSpaceConfig

Sub-configuration dictating the spatial distribution of momentum (k-vectors).

polychromatic PolychromaticConfig

Sub-configuration dictating the spectral/wavelength envelope.

randomize RandomizeConfig

Sub-configuration dictating random noise/coherence features.

intensity_scale float

Scalar multiplier for total field intensity. Must be > 0.

wavelength Union[float, List[float]]

The physical wavelength(s) of the simulation.

pol_vect Tuple[complex, complex]

Jones vector (E1, E2) defining base polarization, normalized upon init.

beam_axis Tuple[float, float, float]

Average propagation direction vector (kx, ky, kz), normalized upon init.

num_modes int

Number of plane wave modes used for the angular spectrum sum.

theta_max float

Maximum half-cone angle (radians) restricting the generated wavevectors.

Source code in src\vectorwaves\config_stuff.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
@dataclass(slots=True)
class SourceConfig(SerializableConfig):
    """
    Light Source Configuration. 

    Aggregates all physical parameters defining the initial state of the light
    field, including nested properties like spatial modes and polarization.
    Access this via `config.source`.

    Example:
        `config.source.intensity_scale = 1e3`
        `config.source.wavelength = 0.532`
        `config.source.k_space.gaussian(sigma_k_perp=2.0)`
        `config.source.randomize.off()`

    Attributes
    ----------
    k_space : KSpaceConfig
        Sub-configuration dictating the spatial distribution of momentum (k-vectors).
    polychromatic : PolychromaticConfig
        Sub-configuration dictating the spectral/wavelength envelope.
    randomize : RandomizeConfig
        Sub-configuration dictating random noise/coherence features.
    intensity_scale : float
        Scalar multiplier for total field intensity. Must be > 0.
    wavelength : Union[float, List[float]]
        The physical wavelength(s) of the simulation.
    pol_vect : Tuple[complex, complex]
        Jones vector (E1, E2) defining base polarization, normalized upon init.
    beam_axis : Tuple[float, float, float]
        Average propagation direction vector (kx, ky, kz), normalized upon init.
    num_modes : int
        Number of plane wave modes used for the angular spectrum sum.
    theta_max : float
        Maximum half-cone angle (radians) restricting the generated wavevectors.
    """
    k_space: KSpaceConfig = field(default_factory=KSpaceConfig)
    polychromatic: PolychromaticConfig = field(default_factory=PolychromaticConfig)
    randomize: RandomizeConfig = field(default_factory=RandomizeConfig)

    intensity_scale: float = 1e3
    wavelength: Union[float, List[float]] = 1.0
    pol_vect: Tuple[complex, complex] = (1+0j, 0+0j)
    beam_axis: Tuple[float, float, float] = (0.0, 0.0, 1.0)

    num_modes: int = 6000
    theta_max: float = np.pi/2

    def validate(self):
        self.intensity_scale = _check_scalar(self.intensity_scale, "source.intensity_scale", float)
        if self.intensity_scale <= 0: raise ValueError("source.intensity_scale must be > 0")

        self.num_modes = _check_scalar(self.num_modes, "source.num_modes", int)
        if self.num_modes <= 0: raise ValueError("source.num_modes must be > 0")

        if np.isscalar(self.wavelength):
            self.wavelength = _check_scalar(self.wavelength, "source.wavelength", float)
            if self.wavelength <= 0: raise ValueError("Wavelength must be > 0")
        else:
            if isinstance(self.wavelength, (np.ndarray, list, tuple)):
                self.wavelength =[_check_scalar(w, "source.wavelength", float) for w in self.wavelength]
                if any(w <= 0 for w in self.wavelength): raise ValueError("All wavelengths must be > 0")
            else:
                 raise TypeError(f"Expected float or list of floats for wavelength, got {type(self.wavelength)}")

        self.pol_vect = _coerce_tuple(self.pol_vect, 2, "source.pol_vect", dtype=complex)
        norm = np.linalg.norm(self.pol_vect)
        if norm < 1e-13: raise ValueError('pol_vect must have reasonable norm.')
        self.pol_vect = (self.pol_vect[0]/norm, self.pol_vect[1]/norm)

        axis = _coerce_tuple(self.beam_axis, 3, "source.beam_axis", dtype=float)
        norm = np.linalg.norm(axis)
        if norm < 1e-13: raise ValueError("beam_axis must have reasonable norm")
        self.beam_axis = (axis[0]/norm, axis[1]/norm, axis[2]/norm)

        self.theta_max = _check_scalar(self.theta_max, "source.theta_max", float)
        if not 0 < self.theta_max <= np.pi:
            raise ValueError("For fibonacci sampling, maximum angle must be in (0, pi).")

        self.k_space.validate()
        self.polychromatic.validate()
        self.randomize.validate()

    def __post_init__(self):
        self.validate()

vectorwaves.config_stuff.OpConfig dataclass

Bases: SerializableConfig

Observation Plane Configuration.

Defines the spatial grid where the optical field is evaluated. Access this via config.op.

Example: config.op.size = (10.0, 10.0) config.op.spacing = 0.05

Attributes:

Name Type Description
spacing float

Grid pixel spacing. Must be strictly > 0.

size tuple[float, float]

Physical size of the rectangular plane (width, height).

center tuple[float, float]

Center position (x, y) of the plane relative to the global origin (0,0).

Source code in src\vectorwaves\config_stuff.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
@dataclass(slots=True)
class OpConfig(SerializableConfig):
    """
    Observation Plane Configuration.

    Defines the spatial grid where the optical field is evaluated. Access this
    via `config.op`.

    Example:
        `config.op.size = (10.0, 10.0)`
        `config.op.spacing = 0.05`

    Attributes
    ----------
    spacing : float
        Grid pixel spacing. Must be strictly > 0.
    size : tuple[float, float]
        Physical size of the rectangular plane (width, height).
    center : tuple[float, float]
        Center position (x, y) of the plane relative to the global origin (0,0).
    """
    spacing: float = 0.05
    size: Tuple[float, float] = (10.0, 10.0)
    center: Tuple[float, float] = (0.0, 0.0)

    def validate(self):
        self.spacing = _check_scalar(self.spacing, "op.spacing", float)
        if self.spacing <= 0: raise ValueError("op.spacing must be > 0")
        self.size = _coerce_tuple(self.size, 2, "op.size", float)
        if any(d <= 0 for d in self.size): raise ValueError("op.size must be positive")
        self.center = _coerce_tuple(self.center, 2, "op.center", float)

    def __post_init__(self):
        self.validate()

vectorwaves.config_stuff.KSpaceConfig dataclass

Bases: SerializableConfig

k-Space Spatial Profile Configuration.

Controls how plane wave modes are weighted depending on their wavevector. Access this via config.source.k_space.

Use fluent methods to modify the distribution: config.source.k_space.gaussian(sigma_k_perp=2.0) config.source.k_space.laguerre_gauss(p=0, l=1)

Attributes:

Name Type Description
profile Callable

The active profile function (from KSpaceSpectra or custom).

params Dict[str, Any]

Keyword arguments dynamically passed into the profile callable.

vectorised bool

If True, indicates the callable supports numpy array vectorization.

Source code in src\vectorwaves\config_stuff.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
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
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
@dataclass(slots=True)
class KSpaceConfig(SerializableConfig):
    """
    k-Space Spatial Profile Configuration.

    Controls how plane wave modes are weighted depending on their wavevector.
    Access this via `config.source.k_space`.

    Use fluent methods to modify the distribution:
        `config.source.k_space.gaussian(sigma_k_perp=2.0)`
        `config.source.k_space.laguerre_gauss(p=0, l=1)`

    Attributes
    ----------
    profile : Callable
        The active profile function (from `KSpaceSpectra` or custom).
    params : Dict[str, Any]
        Keyword arguments dynamically passed into the `profile` callable.
    vectorised : bool
        If True, indicates the callable supports numpy array vectorization.
    """
    profile: Callable = field(default=KSpaceSpectra.gaussian)
    params: Dict[str, Any] = field(default_factory=lambda: {"sigma_k_perp": 1.5})
    vectorised: bool = True

    def uniform(self) -> "KSpaceConfig":
        """
        Sets k-space profile to a flat angular spectrum (Uniform k-space).
        Weights all plane waves equally.
        """
        self.profile = KSpaceSpectra.uniform
        self.params = {}
        return self

    def gaussian(self, sigma_k_perp: float = 1.5) -> "KSpaceConfig":
        """
        Sets k-space profile to a Gaussian envelope.

        Parameters
        ----------
        sigma_k_perp : float, default=1.5
            The standard deviation of the Gaussian envelope in k-space.
            Inversely proportional to the real-space beam waist.
        """
        self.profile = KSpaceSpectra.gaussian
        self.params = {'sigma_k_perp': sigma_k_perp}
        self.vectorised = True
        return self

    def tophat(self, k_perp_max: float = 1.0) -> "KSpaceConfig":
        """
        Sets k-space profile to a sharp Top-Hat cutoff, producing an Airy disk 
        in real space.

        Parameters
        ----------
        k_perp_max : float, default=1.0
            The maximum transverse spatial frequency allowed.
        """
        self.profile = KSpaceSpectra.tophat
        self.params = {'k_perp_max': k_perp_max}
        self.vectorised = True
        return self

    def laguerre_gauss(self, *, p: int = 0, l: int = 0, sigma_k_perp: float = 0.5) -> "KSpaceConfig":
        """
        Sets k-space profile to a Laguerre-Gauss mode carrying 
        Orbital Angular Momentum.

        Parameters
        ----------
        p : int, default=0
            Radial index (p >= 0). Determines the number of radial rings.
        l : int, default=0
            Azimuthal index (topological charge). Determines the OAM.
        sigma_k_perp : float, default=0.5
            Transverse scaling parameter in k-space.
        """
        if p < 0: raise ValueError("LG index p must be >= 0")
        self.profile = KSpaceSpectra.laguerre_gauss
        self.params = {'p': p, 'l': l, 'sigma_k_perp': sigma_k_perp}
        self.vectorised = True
        return self

    def hermite_gauss(self, *, l: int = 0, m: int = 0, sigma_k_perp: float = 0.5) -> "KSpaceConfig":
        """
        Sets k-space profile to a higher-order Hermite-Gauss transverse mode.

        Parameters
        ----------
        l : int, default=0
            Transverse mode index in the x-direction (l >= 0).
        m : int, default=0
            Transverse mode index in the y-direction (m >= 0).
        sigma_k_perp : float, default=0.5
            Transverse scaling parameter in k-space.
        """
        if l < 0 or m < 0: raise ValueError("HG indices l, m must be >= 0")
        self.profile = KSpaceSpectra.hermite_gauss
        self.params = {'l': l, 'm': m, 'sigma_k_perp': sigma_k_perp}
        self.vectorised = True
        return self

    def bessel_gauss(self, *, theta_deg: float = 10.0, sigma_theta: float = 0.05, l: int = 0) -> "KSpaceConfig":
        """
        Sets k-space profile to a Bessel-Gauss mode, creating a non-diffracting core.

        Parameters
        ----------
        theta_deg : float, default=10.0
            Cone opening angle (in degrees) of the Bessel beam in k-space.
        sigma_theta : float, default=0.05
            Angular thickness (in radians) of the Gaussian ring.
        l : int, default=0
            Topological charge. If l != 0, creates a Higher-Order Bessel beam.
        """
        if theta_deg > 70: 
            warnings.warn(
                "Large Bessel cone angles interact strangely with hemisphere clipping.",
                UserWarning
                )
        self.profile = KSpaceSpectra.bessel_gauss
        self.params = {'theta_0': np.radians(theta_deg), 'sigma_theta': sigma_theta, 'l': l}
        self.vectorised = True
        return self

    def custom(self, fn: Callable, vectorised: bool = False, **params) -> "KSpaceConfig":
        """
        Sets a user-defined custom k-space profile function.

        Parameters
        ----------
        fn : Callable
            Function signature: fn(k: np.ndarray, **params) -> complex/np.ndarray
        vectorised : bool, default=False
            If True, `fn` must accept a (3, N) array and return an (N,) array.
            Else: fn must accept a (3,) array and return a complex number.
        **params : Any
            Keyword arguments passed directly into `fn` during evaluation.

        Notes
        -----
        The provided function `fn` will be tested with a dummy wavevector 
        array (np.random.randn(3, 2) if vectorised else np.random.randn(3)) 
        to validate its return type and shape.
        """
        self.profile = fn
        self.params = params
        self.vectorised = vectorised
        self.validate()
        return self

    def validate(self):
        if getattr(self.profile, '__name__', '') == 'custom_callable_fail': return
        try:
            test_k = np.random.randn(3, 2) if self.vectorised else np.random.randn(3)
            res = self.profile(test_k, **self.params)
            if self.vectorised and (not hasattr(res, "__len__") or len(res) != 2): 
                raise ValueError("Vectorised function must return an array matching input shape.")
            elif not self.vectorised and not np.isscalar(res) and not isinstance(res, complex): 
                raise ValueError("Non-vectorised function must return a scalar.")
        except Exception as e:
            raise RuntimeError(f"K space profile failed validation: {e}")

    def __post_init__(self):
        self.validate()

bessel_gauss(*, theta_deg=10.0, sigma_theta=0.05, l=0)

Sets k-space profile to a Bessel-Gauss mode, creating a non-diffracting core.

Parameters:

Name Type Description Default
theta_deg float

Cone opening angle (in degrees) of the Bessel beam in k-space.

10.0
sigma_theta float

Angular thickness (in radians) of the Gaussian ring.

0.05
l int

Topological charge. If l != 0, creates a Higher-Order Bessel beam.

0
Source code in src\vectorwaves\config_stuff.py
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
def bessel_gauss(self, *, theta_deg: float = 10.0, sigma_theta: float = 0.05, l: int = 0) -> "KSpaceConfig":
    """
    Sets k-space profile to a Bessel-Gauss mode, creating a non-diffracting core.

    Parameters
    ----------
    theta_deg : float, default=10.0
        Cone opening angle (in degrees) of the Bessel beam in k-space.
    sigma_theta : float, default=0.05
        Angular thickness (in radians) of the Gaussian ring.
    l : int, default=0
        Topological charge. If l != 0, creates a Higher-Order Bessel beam.
    """
    if theta_deg > 70: 
        warnings.warn(
            "Large Bessel cone angles interact strangely with hemisphere clipping.",
            UserWarning
            )
    self.profile = KSpaceSpectra.bessel_gauss
    self.params = {'theta_0': np.radians(theta_deg), 'sigma_theta': sigma_theta, 'l': l}
    self.vectorised = True
    return self

custom(fn, vectorised=False, **params)

Sets a user-defined custom k-space profile function.

Parameters:

Name Type Description Default
fn Callable

Function signature: fn(k: np.ndarray, **params) -> complex/np.ndarray

required
vectorised bool

If True, fn must accept a (3, N) array and return an (N,) array. Else: fn must accept a (3,) array and return a complex number.

False
**params Any

Keyword arguments passed directly into fn during evaluation.

{}
Notes

The provided function fn will be tested with a dummy wavevector array (np.random.randn(3, 2) if vectorised else np.random.randn(3)) to validate its return type and shape.

Source code in src\vectorwaves\config_stuff.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def custom(self, fn: Callable, vectorised: bool = False, **params) -> "KSpaceConfig":
    """
    Sets a user-defined custom k-space profile function.

    Parameters
    ----------
    fn : Callable
        Function signature: fn(k: np.ndarray, **params) -> complex/np.ndarray
    vectorised : bool, default=False
        If True, `fn` must accept a (3, N) array and return an (N,) array.
        Else: fn must accept a (3,) array and return a complex number.
    **params : Any
        Keyword arguments passed directly into `fn` during evaluation.

    Notes
    -----
    The provided function `fn` will be tested with a dummy wavevector 
    array (np.random.randn(3, 2) if vectorised else np.random.randn(3)) 
    to validate its return type and shape.
    """
    self.profile = fn
    self.params = params
    self.vectorised = vectorised
    self.validate()
    return self

gaussian(sigma_k_perp=1.5)

Sets k-space profile to a Gaussian envelope.

Parameters:

Name Type Description Default
sigma_k_perp float

The standard deviation of the Gaussian envelope in k-space. Inversely proportional to the real-space beam waist.

1.5
Source code in src\vectorwaves\config_stuff.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
def gaussian(self, sigma_k_perp: float = 1.5) -> "KSpaceConfig":
    """
    Sets k-space profile to a Gaussian envelope.

    Parameters
    ----------
    sigma_k_perp : float, default=1.5
        The standard deviation of the Gaussian envelope in k-space.
        Inversely proportional to the real-space beam waist.
    """
    self.profile = KSpaceSpectra.gaussian
    self.params = {'sigma_k_perp': sigma_k_perp}
    self.vectorised = True
    return self

hermite_gauss(*, l=0, m=0, sigma_k_perp=0.5)

Sets k-space profile to a higher-order Hermite-Gauss transverse mode.

Parameters:

Name Type Description Default
l int

Transverse mode index in the x-direction (l >= 0).

0
m int

Transverse mode index in the y-direction (m >= 0).

0
sigma_k_perp float

Transverse scaling parameter in k-space.

0.5
Source code in src\vectorwaves\config_stuff.py
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
def hermite_gauss(self, *, l: int = 0, m: int = 0, sigma_k_perp: float = 0.5) -> "KSpaceConfig":
    """
    Sets k-space profile to a higher-order Hermite-Gauss transverse mode.

    Parameters
    ----------
    l : int, default=0
        Transverse mode index in the x-direction (l >= 0).
    m : int, default=0
        Transverse mode index in the y-direction (m >= 0).
    sigma_k_perp : float, default=0.5
        Transverse scaling parameter in k-space.
    """
    if l < 0 or m < 0: raise ValueError("HG indices l, m must be >= 0")
    self.profile = KSpaceSpectra.hermite_gauss
    self.params = {'l': l, 'm': m, 'sigma_k_perp': sigma_k_perp}
    self.vectorised = True
    return self

laguerre_gauss(*, p=0, l=0, sigma_k_perp=0.5)

Sets k-space profile to a Laguerre-Gauss mode carrying Orbital Angular Momentum.

Parameters:

Name Type Description Default
p int

Radial index (p >= 0). Determines the number of radial rings.

0
l int

Azimuthal index (topological charge). Determines the OAM.

0
sigma_k_perp float

Transverse scaling parameter in k-space.

0.5
Source code in src\vectorwaves\config_stuff.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
def laguerre_gauss(self, *, p: int = 0, l: int = 0, sigma_k_perp: float = 0.5) -> "KSpaceConfig":
    """
    Sets k-space profile to a Laguerre-Gauss mode carrying 
    Orbital Angular Momentum.

    Parameters
    ----------
    p : int, default=0
        Radial index (p >= 0). Determines the number of radial rings.
    l : int, default=0
        Azimuthal index (topological charge). Determines the OAM.
    sigma_k_perp : float, default=0.5
        Transverse scaling parameter in k-space.
    """
    if p < 0: raise ValueError("LG index p must be >= 0")
    self.profile = KSpaceSpectra.laguerre_gauss
    self.params = {'p': p, 'l': l, 'sigma_k_perp': sigma_k_perp}
    self.vectorised = True
    return self

tophat(k_perp_max=1.0)

Sets k-space profile to a sharp Top-Hat cutoff, producing an Airy disk in real space.

Parameters:

Name Type Description Default
k_perp_max float

The maximum transverse spatial frequency allowed.

1.0
Source code in src\vectorwaves\config_stuff.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def tophat(self, k_perp_max: float = 1.0) -> "KSpaceConfig":
    """
    Sets k-space profile to a sharp Top-Hat cutoff, producing an Airy disk 
    in real space.

    Parameters
    ----------
    k_perp_max : float, default=1.0
        The maximum transverse spatial frequency allowed.
    """
    self.profile = KSpaceSpectra.tophat
    self.params = {'k_perp_max': k_perp_max}
    self.vectorised = True
    return self

uniform()

Sets k-space profile to a flat angular spectrum (Uniform k-space). Weights all plane waves equally.

Source code in src\vectorwaves\config_stuff.py
326
327
328
329
330
331
332
333
def uniform(self) -> "KSpaceConfig":
    """
    Sets k-space profile to a flat angular spectrum (Uniform k-space).
    Weights all plane waves equally.
    """
    self.profile = KSpaceSpectra.uniform
    self.params = {}
    return self

vectorwaves.config_stuff.PolychromaticConfig dataclass

Bases: SerializableConfig

Polychromatic Envelope Configuration.

Controls the spectral (wavelength) distribution of the beam. Access this via config.source.polychromatic.

Use fluent methods to modify the distribution: config.source.polychromatic.gaussian(center=8.0, sigma=0.5)

Attributes:

Name Type Description
profile Callable

The active profile function (from PolychromaticSpectra or custom).

params Dict[str, Any]

Keyword arguments passed into the profile callable.

Source code in src\vectorwaves\config_stuff.py
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
@dataclass(slots=True)
class PolychromaticConfig(SerializableConfig):
    """
    Polychromatic Envelope Configuration.

    Controls the spectral (wavelength) distribution of the beam. Access this
    via `config.source.polychromatic`.

    Use fluent methods to modify the distribution:
        `config.source.polychromatic.gaussian(center=8.0, sigma=0.5)`

    Attributes
    ----------
    profile : Callable
        The active profile function (from `PolychromaticSpectra` or custom).
    params : Dict[str, Any]
        Keyword arguments passed into the `profile` callable.
    """
    profile: Callable = field(default=PolychromaticSpectra.uniform)
    params: Dict[str, Any] = field(default_factory=dict)

    def uniform(self) -> "PolychromaticConfig":
        """
        Sets a flat spectral envelope (all wavelengths weighted equally).
        """
        self.profile = PolychromaticSpectra.uniform
        self.params = {}
        return self

    def gaussian(self, center: float = 8.0, sigma: float = 0.5) -> "PolychromaticConfig":
        """
        Sets a Gaussian spectral weight distribution.

        Parameters
        ----------
        center : float, default=8.0
            The central wavelength (mean).
        sigma : float, default=0.5
            The spectral bandwidth (standard deviation).
        """
        self.profile = PolychromaticSpectra.gaussian
        self.params = {'center': center, 'sigma': sigma}
        return self

    def lorentzian(self, center: float = 8.0, gamma: float = 0.5) -> "PolychromaticConfig":
        """
        Sets a Lorentzian spectral weight distribution.

        Parameters
        ----------
        center : float, default=8.0
            The central resonance wavelength.
        gamma : float, default=0.5
            The Half-Width at Half-Maximum of the spectrum.
        """
        self.profile = PolychromaticSpectra.lorentzian
        self.params = {'center': center, 'gamma': gamma}
        return self

    def tophat(self, center: float = 8.0, width: float = 1.0) -> "PolychromaticConfig":
        """
        Sets a Top-Hat bandpass spectral envelope.

        Parameters
        ----------
        center : float, default=8.0
            The central wavelength of the bandpass.
        width : float, default=1.0
            The total spectral width of the bandpass.
        """
        self.profile = PolychromaticSpectra.tophat
        self.params = {'center': center, 'width': width}
        return self

    def custom(self, fn: Callable, **params) -> "PolychromaticConfig":
        """
        Sets a user-defined custom polychromatic envelope.

        Parameters
        ----------
        fn : Callable
            Function signature: fn(wavelength: float, **params) -> float
        **params : Any
            Keyword arguments passed directly into `fn` during evaluation.

        Notes
        -----
        The provided function `fn` will be tested with a dummy wavelength 
        (8.0) to validate that it returns a finite scalar.
        """
        self.profile = fn
        self.params = params
        self.validate()
        return self

    def validate(self):
        if getattr(self.profile, '__name__', '').endswith('fail'): return
        try:
            result = self.profile(8.0, **self.params)
            if not np.isscalar(result): raise ValueError(f"Polychromatic envelope must return a scalar. Got {type(result)}.")
            if not np.isfinite(result): raise ValueError(f"Polychromatic envelope returned non-finite value: {result}")
        except Exception as e:
            raise RuntimeError(f"Polychromatic profile failed validation: {e}\nEnsure signature is: fn(wavelength: float, **params) -> float") from e

    def __post_init__(self):
        self.validate()

custom(fn, **params)

Sets a user-defined custom polychromatic envelope.

Parameters:

Name Type Description Default
fn Callable

Function signature: fn(wavelength: float, **params) -> float

required
**params Any

Keyword arguments passed directly into fn during evaluation.

{}
Notes

The provided function fn will be tested with a dummy wavelength (8.0) to validate that it returns a finite scalar.

Source code in src\vectorwaves\config_stuff.py
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
def custom(self, fn: Callable, **params) -> "PolychromaticConfig":
    """
    Sets a user-defined custom polychromatic envelope.

    Parameters
    ----------
    fn : Callable
        Function signature: fn(wavelength: float, **params) -> float
    **params : Any
        Keyword arguments passed directly into `fn` during evaluation.

    Notes
    -----
    The provided function `fn` will be tested with a dummy wavelength 
    (8.0) to validate that it returns a finite scalar.
    """
    self.profile = fn
    self.params = params
    self.validate()
    return self

gaussian(center=8.0, sigma=0.5)

Sets a Gaussian spectral weight distribution.

Parameters:

Name Type Description Default
center float

The central wavelength (mean).

8.0
sigma float

The spectral bandwidth (standard deviation).

0.5
Source code in src\vectorwaves\config_stuff.py
498
499
500
501
502
503
504
505
506
507
508
509
510
511
def gaussian(self, center: float = 8.0, sigma: float = 0.5) -> "PolychromaticConfig":
    """
    Sets a Gaussian spectral weight distribution.

    Parameters
    ----------
    center : float, default=8.0
        The central wavelength (mean).
    sigma : float, default=0.5
        The spectral bandwidth (standard deviation).
    """
    self.profile = PolychromaticSpectra.gaussian
    self.params = {'center': center, 'sigma': sigma}
    return self

lorentzian(center=8.0, gamma=0.5)

Sets a Lorentzian spectral weight distribution.

Parameters:

Name Type Description Default
center float

The central resonance wavelength.

8.0
gamma float

The Half-Width at Half-Maximum of the spectrum.

0.5
Source code in src\vectorwaves\config_stuff.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
def lorentzian(self, center: float = 8.0, gamma: float = 0.5) -> "PolychromaticConfig":
    """
    Sets a Lorentzian spectral weight distribution.

    Parameters
    ----------
    center : float, default=8.0
        The central resonance wavelength.
    gamma : float, default=0.5
        The Half-Width at Half-Maximum of the spectrum.
    """
    self.profile = PolychromaticSpectra.lorentzian
    self.params = {'center': center, 'gamma': gamma}
    return self

tophat(center=8.0, width=1.0)

Sets a Top-Hat bandpass spectral envelope.

Parameters:

Name Type Description Default
center float

The central wavelength of the bandpass.

8.0
width float

The total spectral width of the bandpass.

1.0
Source code in src\vectorwaves\config_stuff.py
528
529
530
531
532
533
534
535
536
537
538
539
540
541
def tophat(self, center: float = 8.0, width: float = 1.0) -> "PolychromaticConfig":
    """
    Sets a Top-Hat bandpass spectral envelope.

    Parameters
    ----------
    center : float, default=8.0
        The central wavelength of the bandpass.
    width : float, default=1.0
        The total spectral width of the bandpass.
    """
    self.profile = PolychromaticSpectra.tophat
    self.params = {'center': center, 'width': width}
    return self

uniform()

Sets a flat spectral envelope (all wavelengths weighted equally).

Source code in src\vectorwaves\config_stuff.py
490
491
492
493
494
495
496
def uniform(self) -> "PolychromaticConfig":
    """
    Sets a flat spectral envelope (all wavelengths weighted equally).
    """
    self.profile = PolychromaticSpectra.uniform
    self.params = {}
    return self

vectorwaves.config_stuff.RandomizeConfig dataclass

Bases: SerializableConfig

Stochastic Process Configuration.

Controls which aspects of the light field are randomized during generation to simulate partially coherent beams, diffuse light, or specific speckle stats. Access this via config.source.randomize.

Example: To simulate fully coherent deterministic light: config.source.randomize.off()

Attributes:

Name Type Description
seed int

Seed for the random number generator ensuring reproducible fields.

phase_max float

Applies a random phase offset phi to each mode drawn from Uniform(-phase_max, +phase_max). This controls the degree of phase randomness: - phase_max = 0 for fully deterministic phase - 0 < phase_max < pi for partially randomized phase - phase_max = pi for fully randomized phase Must lie in [0, pi].

pol_rot_max float

Maximum random rotation angle applied to the polarization vector drawn from Uniform(-pol_rot_max, +pol_rot_max) Controls the spread of polarization orientations: - pol_rot_max = 0 for deterministic polarization orientation - 0 < pol_rot_max < pi for partially randomized orientation - pol_rot_max = pi for fully randomized orientation Must lie in [0, pi].

Ignored if pol_state=True.

pol_state bool

If True, samples fully random polarization states from the Poincaré sphere. Overrides pol_rot_max.

amplitude bool

If True, applies a complex random normal factor to the mode amplitude.

Source code in src\vectorwaves\config_stuff.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
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
292
293
294
295
296
297
298
299
@dataclass(slots=True)
class RandomizeConfig(SerializableConfig):
    """
    Stochastic Process Configuration.

    Controls which aspects of the light field are randomized during generation
    to simulate partially coherent beams, diffuse light, or specific speckle stats.
    Access this via `config.source.randomize`.

    Example:
        To simulate fully coherent deterministic light:
        `config.source.randomize.off()`

    Attributes
    ----------
    seed : int
        Seed for the random number generator ensuring reproducible fields.

    phase_max : float
        Applies a random phase offset phi to each mode 
        drawn from Uniform(-phase_max, +phase_max).
        This controls the degree of phase randomness:
        - phase_max = 0      for fully deterministic phase
        - 0 < phase_max < pi for partially randomized phase
        - phase_max = pi     for fully randomized phase
        Must lie in [0, pi].

    pol_rot_max : float
        Maximum random rotation angle applied to the polarization vector 
        drawn from Uniform(-pol_rot_max, +pol_rot_max)
        Controls the spread of polarization orientations:
        - pol_rot_max = 0        for deterministic polarization orientation
        - 0 < pol_rot_max < pi for partially randomized orientation
        - pol_rot_max = pi     for fully randomized orientation
        Must lie in [0, pi].

        Ignored if `pol_state=True`.

    pol_state : bool
        If True, samples fully random polarization states from the Poincaré sphere.
        Overrides `pol_rot_max`.

    amplitude : bool
        If True, applies a complex random normal factor to the mode amplitude.

    """
    seed: int = 24459
    pol_rot_max: float = np.pi
    phase_max  : float = np.pi
    pol_state  : bool  = False
    amplitude  : bool  = True

    def validate(self):
        for f in["pol_state", "amplitude"]:
            val = getattr(self, f)
            if not isinstance(val, (bool, np.bool_)):
                raise TypeError(f"Config Error ['randomize.{f}']: Expected bool, got {type(val).__name__}")
        self.seed = _check_scalar(self.seed, "randomize.seed", int)

        self.phase_max = _check_scalar(self.phase_max, "randomize.phase_max", float)
        if not 0 <= self.phase_max <= np.pi:
            raise ValueError("Random phase_max must be in [0, pi].")
        self.pol_rot_max = _check_scalar(self.pol_rot_max, "randomize.pol_rot_max", float)
        if not 0 <= self.pol_rot_max <= np.pi:
            raise ValueError("Random pol_rot_max must be in [0, pi].")

        if self.pol_state and self.pol_rot_max > 0:
            warnings.warn(
                "'pol_state' is True and 'pol_rot_max' is present. 'pol_state' overrides 'pol_rot_max'.", 
                UserWarning
                )

    def off(self) -> "RandomizeConfig":
        """Turns off all randomness, making the beam perfectly coherent and deterministic."""
        self.pol_rot_max = 0
        self.phase_max = 0
        self.pol_state = False
        self.amplitude = False
        return self

    def __post_init__(self):
        self.validate()

off()

Turns off all randomness, making the beam perfectly coherent and deterministic.

Source code in src\vectorwaves\config_stuff.py
290
291
292
293
294
295
296
def off(self) -> "RandomizeConfig":
    """Turns off all randomness, making the beam perfectly coherent and deterministic."""
    self.pol_rot_max = 0
    self.phase_max = 0
    self.pol_state = False
    self.amplitude = False
    return self