Skip to content

scatteringmodel API

mixscatter.scatteringmodel

Calculate form factors of multicomponent systems.

This module provides classes and functions for calculating form factors of multicomponent systems. It includes definitions for various layer profiles, particles composed of these layers, and models to calculate scattering properties for these particles.

Classes:

Name Description
LayerProfile

Defines the interface for layer profiles.

EmptyProfile

Represents an empty layer profile.

ConstantProfile

Represents a layer profile with constant contrast.

LinearProfile

Represents a layer profile with linearly varying contrast.

Particle

Represents a particle composed of multiple layers.

ParticleBuilder

A builder class for constructing Particle instances.

ScatteringModel

Calculates scattering properties for a list of particles.

SimpleSphere

A convenience class for creating a scattering model of homogeneously scattering spheres.

SimpleCoreShell

A convenience class for creating a scattering model of core-shell particles.

SimpleGradient

A convenience class for creating a scattering model with gradient profiles.

Examples:

Create a simple scattering model for spheres:

>>> from mixscatter.mixture import Mixture
>>> import numpy as np
>>> wavevector = np.linspace(0.01, 1.0, 100)
>>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
>>> model = SimpleSphere(wavevector, mixture, contrast=1.0)
>>> form_factor = model.average_form_factor

Create a core-shell particle model:

>>> model = SimpleCoreShell(wavevector, mixture, core_to_total_ratio=0.5, core_contrast=1.0, shell_contrast=0.5)
>>> form_factor = model.average_form_factor

Create a gradient profile particle model:

>>> model = SimpleGradient(wavevector, mixture, center_contrast=1.0, boundary_contrast=0.5)
>>> form_factor = model.average_form_factor

ConstantProfile

Bases: LayerProfile

Represents a layer profile with constant contrast.

Attributes:

Name Type Description
radius_inner float

Inner radius of the layer.

radius_outer float

Outer radius of the layer.

contrast float

Scattering contrast of the layer.

Methods:

Name Description
__init__

Initialize a constant layer profile.

calculate_amplitude

Calculate the amplitude for the given wavevector.

calculate_forward_amplitude

Calculate the forward amplitude.

get_profile

Get the profile for the given distance from the origin.

calculate_second_moment

Calculate the second moment.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class ConstantProfile(LayerProfile):
    """
    Represents a layer profile with constant contrast.

    Attributes:
        radius_inner (float): Inner radius of the layer.
        radius_outer (float): Outer radius of the layer.
        contrast (float): Scattering contrast of the layer.

    Methods:
        __init__(radius_inner, radius_outer, contrast):
            Initialize a constant layer profile.

        calculate_amplitude(wavevector):
            Calculate the amplitude for the given wavevector.

        calculate_forward_amplitude():
            Calculate the forward amplitude.

        get_profile(distance):
            Get the profile for the given distance from the origin.

        calculate_second_moment():
            Calculate the second moment.
    """

    def __init__(self, radius_inner: float, radius_outer: float, contrast: float) -> None:
        """
        Initialize a constant layer profile.

        Args:
            radius_inner: Inner radius of the layer.
            radius_outer: Outer radius of the layer.
            contrast: Scattering contrast of the layer.

        Raises:
            RuntimeError: If `radius_inner` is greater than `radius_outer`.
        """
        if radius_inner > radius_outer:
            raise RuntimeError("'radius_inner' must be smaller than 'radius_outer'.")

        self.radius_inner = radius_inner
        self.radius_outer = radius_outer
        self.contrast = contrast

    def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
        """
        Calculate the amplitude for the given wavevector.

        Args:
            wavevector: Scattering wavevector.

        Returns:
            Calculated amplitude array.
        """
        wavevector = np.asarray(wavevector, dtype=np.float64)
        QR_outer = wavevector * self.radius_outer
        QR_inner = wavevector * self.radius_inner
        amplitude: NDArray[np.float64] = np.sin(QR_outer) - QR_outer * np.cos(QR_outer)
        amplitude -= np.sin(QR_inner) - QR_inner * np.cos(QR_inner)
        amplitude *= 4.0 * np.pi / wavevector**3 * self.contrast
        return amplitude

    def calculate_forward_amplitude(self) -> float:
        """Calculate the forward amplitude.

        Returns:
            The calculated forward amplitude.
        """
        return 4.0 / 3.0 * np.pi * (self.radius_outer**3 - self.radius_inner**3) * self.contrast

    def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
        """Get the profile for the given distance from the origin.

        Args:
            distance: Distance from the origin.

        Returns:
            The profile evaluated on the distance array.
        """
        distance = np.asarray(distance, dtype=np.float64)
        distance_mask = (distance >= self.radius_inner) & (distance < self.radius_outer)
        return np.where(distance_mask, self.contrast, 0.0)

    def calculate_second_moment(self) -> float:
        """Calculate the second moment.

        Returns:
            The calculated second moment.
        """
        return 4.0 / 5.0 * np.pi * (self.radius_outer**5 - self.radius_inner**5) * self.contrast

__init__(radius_inner, radius_outer, contrast)

Initialize a constant layer profile.

Parameters:

Name Type Description Default
radius_inner float

Inner radius of the layer.

required
radius_outer float

Outer radius of the layer.

required
contrast float

Scattering contrast of the layer.

required

Raises:

Type Description
RuntimeError

If radius_inner is greater than radius_outer.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self, radius_inner: float, radius_outer: float, contrast: float) -> None:
    """
    Initialize a constant layer profile.

    Args:
        radius_inner: Inner radius of the layer.
        radius_outer: Outer radius of the layer.
        contrast: Scattering contrast of the layer.

    Raises:
        RuntimeError: If `radius_inner` is greater than `radius_outer`.
    """
    if radius_inner > radius_outer:
        raise RuntimeError("'radius_inner' must be smaller than 'radius_outer'.")

    self.radius_inner = radius_inner
    self.radius_outer = radius_outer
    self.contrast = contrast

calculate_amplitude(wavevector)

Calculate the amplitude for the given wavevector.

Parameters:

Name Type Description Default
wavevector ArrayLike

Scattering wavevector.

required

Returns:

Type Description
NDArray[float64]

Calculated amplitude array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
    """
    Calculate the amplitude for the given wavevector.

    Args:
        wavevector: Scattering wavevector.

    Returns:
        Calculated amplitude array.
    """
    wavevector = np.asarray(wavevector, dtype=np.float64)
    QR_outer = wavevector * self.radius_outer
    QR_inner = wavevector * self.radius_inner
    amplitude: NDArray[np.float64] = np.sin(QR_outer) - QR_outer * np.cos(QR_outer)
    amplitude -= np.sin(QR_inner) - QR_inner * np.cos(QR_inner)
    amplitude *= 4.0 * np.pi / wavevector**3 * self.contrast
    return amplitude

calculate_forward_amplitude()

Calculate the forward amplitude.

Returns:

Type Description
float

The calculated forward amplitude.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_forward_amplitude(self) -> float:
    """Calculate the forward amplitude.

    Returns:
        The calculated forward amplitude.
    """
    return 4.0 / 3.0 * np.pi * (self.radius_outer**3 - self.radius_inner**3) * self.contrast

calculate_second_moment()

Calculate the second moment.

Returns:

Type Description
float

The calculated second moment.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_second_moment(self) -> float:
    """Calculate the second moment.

    Returns:
        The calculated second moment.
    """
    return 4.0 / 5.0 * np.pi * (self.radius_outer**5 - self.radius_inner**5) * self.contrast

get_profile(distance)

Get the profile for the given distance from the origin.

Parameters:

Name Type Description Default
distance ArrayLike

Distance from the origin.

required

Returns:

Type Description
NDArray[float64]

The profile evaluated on the distance array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
    """Get the profile for the given distance from the origin.

    Args:
        distance: Distance from the origin.

    Returns:
        The profile evaluated on the distance array.
    """
    distance = np.asarray(distance, dtype=np.float64)
    distance_mask = (distance >= self.radius_inner) & (distance < self.radius_outer)
    return np.where(distance_mask, self.contrast, 0.0)

EmptyProfile

Bases: LayerProfile

Represents an empty layer profile.

Attributes:

Name Type Description
radius_inner float

Inner radius of the empty layer.

radius_outer float

Outer radius of the empty layer.

Methods:

Name Description
__init__

Initialize an empty layer profile.

calculate_amplitude

Calculate the amplitude for the given wavevector.

calculate_forward_amplitude

Calculate the forward amplitude.

get_profile

Get the profile for the given distance from the origin.

calculate_second_moment

Calculate the second moment.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class EmptyProfile(LayerProfile):
    """
    Represents an empty layer profile.

    Attributes:
        radius_inner (float): Inner radius of the empty layer.
        radius_outer (float): Outer radius of the empty layer.

    Methods:
        __init__(radius_inner, radius_outer):
            Initialize an empty layer profile.

        calculate_amplitude(wavevector):
            Calculate the amplitude for the given wavevector.

        calculate_forward_amplitude():
            Calculate the forward amplitude.

        get_profile(distance):
            Get the profile for the given distance from the origin.

        calculate_second_moment():
            Calculate the second moment.
    """

    def __init__(self, radius_inner: float, radius_outer: float) -> None:
        """
        Initialize an empty layer profile.

        Args:
            radius_inner: Inner radius of the empty layer.
            radius_outer: Outer radius of the empty layer.

        Raises:
            RuntimeError: If `radius_inner` is greater than `radius_outer`.
        """
        if radius_inner > radius_outer:
            raise RuntimeError("'radius_inner' must be smaller than 'radius_outer'.")

        self.radius_inner = radius_inner
        self.radius_outer = radius_outer

    def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
        """
        Calculate the amplitude for the given wavevector.

        Args:
            wavevector: Scattering wavevector.

        Returns:
            Zero array of the same shape as wavevector.
        """
        wavevector = np.asarray(wavevector, dtype=np.float64)
        return np.zeros_like(wavevector)

    def calculate_forward_amplitude(self) -> float:
        """Calculate the forward amplitude.

        Returns:
            Zero.
        """
        return 0.0

    def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
        """
        Get the profile for the given distance from the origin.

        Args:
            distance: Distance from the origin.

        Returns:
            Zero array of the same shape as distance.
        """
        distance = np.asarray(distance, dtype=np.float64)
        return np.zeros_like(distance)

    def calculate_second_moment(self) -> float:
        """Calculate the second moment.

        Returns:
            Zero.
        """
        return 0.0

__init__(radius_inner, radius_outer)

Initialize an empty layer profile.

Parameters:

Name Type Description Default
radius_inner float

Inner radius of the empty layer.

required
radius_outer float

Outer radius of the empty layer.

required

Raises:

Type Description
RuntimeError

If radius_inner is greater than radius_outer.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self, radius_inner: float, radius_outer: float) -> None:
    """
    Initialize an empty layer profile.

    Args:
        radius_inner: Inner radius of the empty layer.
        radius_outer: Outer radius of the empty layer.

    Raises:
        RuntimeError: If `radius_inner` is greater than `radius_outer`.
    """
    if radius_inner > radius_outer:
        raise RuntimeError("'radius_inner' must be smaller than 'radius_outer'.")

    self.radius_inner = radius_inner
    self.radius_outer = radius_outer

calculate_amplitude(wavevector)

Calculate the amplitude for the given wavevector.

Parameters:

Name Type Description Default
wavevector ArrayLike

Scattering wavevector.

required

Returns:

Type Description
NDArray[float64]

Zero array of the same shape as wavevector.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
    """
    Calculate the amplitude for the given wavevector.

    Args:
        wavevector: Scattering wavevector.

    Returns:
        Zero array of the same shape as wavevector.
    """
    wavevector = np.asarray(wavevector, dtype=np.float64)
    return np.zeros_like(wavevector)

calculate_forward_amplitude()

Calculate the forward amplitude.

Returns:

Type Description
float

Zero.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_forward_amplitude(self) -> float:
    """Calculate the forward amplitude.

    Returns:
        Zero.
    """
    return 0.0

calculate_second_moment()

Calculate the second moment.

Returns:

Type Description
float

Zero.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_second_moment(self) -> float:
    """Calculate the second moment.

    Returns:
        Zero.
    """
    return 0.0

get_profile(distance)

Get the profile for the given distance from the origin.

Parameters:

Name Type Description Default
distance ArrayLike

Distance from the origin.

required

Returns:

Type Description
NDArray[float64]

Zero array of the same shape as distance.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
    """
    Get the profile for the given distance from the origin.

    Args:
        distance: Distance from the origin.

    Returns:
        Zero array of the same shape as distance.
    """
    distance = np.asarray(distance, dtype=np.float64)
    return np.zeros_like(distance)

LayerProfile

Bases: Protocol

Defines the interface for layer profiles.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class LayerProfile(Protocol):  # pragma: no cover
    """Defines the interface for layer profiles."""

    radius_inner: float
    radius_outer: float

    def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
        """Calculate the amplitude for the given wavevector."""
        ...

    def calculate_forward_amplitude(self) -> float:
        """Calculate the forward amplitude."""
        ...

    def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
        """Get the profile for the given distance from the origin."""
        ...

    def calculate_second_moment(self) -> float:
        """Calculate the second moment."""
        ...

calculate_amplitude(wavevector)

Calculate the amplitude for the given wavevector.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
    """Calculate the amplitude for the given wavevector."""
    ...

calculate_forward_amplitude()

Calculate the forward amplitude.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_forward_amplitude(self) -> float:
    """Calculate the forward amplitude."""
    ...

calculate_second_moment()

Calculate the second moment.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_second_moment(self) -> float:
    """Calculate the second moment."""
    ...

get_profile(distance)

Get the profile for the given distance from the origin.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
    """Get the profile for the given distance from the origin."""
    ...

LinearProfile

Bases: LayerProfile

Represents a layer profile with linearly varying contrast.

Attributes:

Name Type Description
radius_inner float

Inner radius of the layer.

radius_outer float

Outer radius of the layer.

contrast_inner float

Contrast at the inner radius.

contrast_outer float

Contrast at the outer radius.

Methods:

Name Description
__init__

Initialize a linearly varying layer profile.

calculate_amplitude

Calculate the amplitude for the given wavevector.

calculate_forward_amplitude

Calculate the forward amplitude.

get_profile

Get the profile for the given distance from the origin.

calculate_second_moment

Calculate the second moment.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class LinearProfile(LayerProfile):
    """
    Represents a layer profile with linearly varying contrast.

    Attributes:
        radius_inner (float): Inner radius of the layer.
        radius_outer (float): Outer radius of the layer.
        contrast_inner (float): Contrast at the inner radius.
        contrast_outer (float): Contrast at the outer radius.

    Methods:
        __init__(radius_inner, radius_outer, contrast_inner, contrast_outer):
            Initialize a linearly varying layer profile.

        calculate_amplitude(wavevector):
            Calculate the amplitude for the given wavevector.

        calculate_forward_amplitude():
            Calculate the forward amplitude.

        get_profile(distance):
            Get the profile for the given distance from the origin.

        calculate_second_moment():
            Calculate the second moment.
    """

    def __init__(self, radius_inner: float, radius_outer: float, contrast_inner: float, contrast_outer: float) -> None:
        """
        Initialize a linearly varying layer profile.

        Args:
            radius_inner (float): Inner radius of the layer.
            radius_outer (float): Outer radius of the layer.
            contrast_inner (float): Contrast at the inner radius.
            contrast_outer (float): Contrast at the outer radius.

        Raises:
            RuntimeError: If radius_inner is greater than radius_outer.
        """
        if radius_inner > radius_outer:
            raise RuntimeError("'radius_inner' must be smaller than 'radius_outer'.")

        self.radius_inner = radius_inner
        self.radius_outer = radius_outer
        self.contrast_inner = contrast_inner
        self.contrast_outer = contrast_outer

    def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
        """Calculate the amplitude for the given wavevector.

        Args:
            wavevector: Scattering wavevector.

        Returns:
            The calculated amplitude array.
        """
        wavevector = np.asarray(wavevector, dtype=np.float64)
        QR_outer = wavevector * self.radius_outer
        QR_inner = wavevector * self.radius_inner
        intercept, slope = self._two_point_to_slope_intercept(
            self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
        )
        amplitude_intercept = ConstantProfile(self.radius_inner, self.radius_outer, intercept).calculate_amplitude(
            wavevector
        )
        amplitude_gradient: NDArray[np.float64] = (2.0 - QR_outer**2) * np.cos(QR_outer) + 2.0 * QR_outer * np.sin(
            QR_outer
        )
        amplitude_gradient -= (2.0 - QR_inner**2) * np.cos(QR_inner) + 2.0 * QR_inner * np.sin(QR_inner)
        amplitude_gradient *= 4.0 * np.pi / wavevector**4 * slope
        return amplitude_intercept + amplitude_gradient

    def calculate_forward_amplitude(self) -> float:
        """Calculate the forward amplitude.

        Returns:
            The calculated forward amplitude.
        """
        intercept, slope = self._two_point_to_slope_intercept(
            self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
        )
        forward_amplitude_intercept = ConstantProfile(
            self.radius_inner, self.radius_outer, intercept
        ).calculate_forward_amplitude()
        forward_amplitude_gradient = np.pi * (self.radius_outer**4 - self.radius_inner**4) * slope
        return forward_amplitude_intercept + forward_amplitude_gradient

    @staticmethod
    def _two_point_to_slope_intercept(x_1: float, y_1: float, x_2: float, y_2: float) -> tuple[float, float]:
        """Calculate the slope and intercept for a linear function passing through two points.

        Returns:
            A tuple containing the intercept and slope.
        """
        intercept = (x_2 * y_1 - x_1 * y_2) / (x_2 - x_1)
        slope = (y_2 - y_1) / (x_2 - x_1)
        return intercept, slope

    def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
        """Get the profile for the given distance from the origin.

        Args:
            distance: Distance from the origin.

        Returns:
            The profile evaluated on the distance array.
        """
        distance = np.asarray(distance, dtype=np.float64)
        distance_mask = (distance >= self.radius_inner) & (distance < self.radius_outer)
        intercept, slope = self._two_point_to_slope_intercept(
            self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
        )
        profile = np.zeros_like(distance)
        profile[distance_mask] = intercept + slope * distance[distance_mask]
        return profile

    def calculate_second_moment(self) -> float:
        """Calculate the second moment.

        Returns:
            The calculated second moment.
        """
        intercept, slope = self._two_point_to_slope_intercept(
            self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
        )
        moment_intercept = ConstantProfile(self.radius_inner, self.radius_outer, intercept).calculate_second_moment()
        moment_slope = 2.0 / 3.0 * np.pi * (self.radius_outer**6 - self.radius_inner**6) * slope
        return moment_intercept + moment_slope

__init__(radius_inner, radius_outer, contrast_inner, contrast_outer)

Initialize a linearly varying layer profile.

Parameters:

Name Type Description Default
radius_inner float

Inner radius of the layer.

required
radius_outer float

Outer radius of the layer.

required
contrast_inner float

Contrast at the inner radius.

required
contrast_outer float

Contrast at the outer radius.

required

Raises:

Type Description
RuntimeError

If radius_inner is greater than radius_outer.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self, radius_inner: float, radius_outer: float, contrast_inner: float, contrast_outer: float) -> None:
    """
    Initialize a linearly varying layer profile.

    Args:
        radius_inner (float): Inner radius of the layer.
        radius_outer (float): Outer radius of the layer.
        contrast_inner (float): Contrast at the inner radius.
        contrast_outer (float): Contrast at the outer radius.

    Raises:
        RuntimeError: If radius_inner is greater than radius_outer.
    """
    if radius_inner > radius_outer:
        raise RuntimeError("'radius_inner' must be smaller than 'radius_outer'.")

    self.radius_inner = radius_inner
    self.radius_outer = radius_outer
    self.contrast_inner = contrast_inner
    self.contrast_outer = contrast_outer

_two_point_to_slope_intercept(x_1, y_1, x_2, y_2) staticmethod

Calculate the slope and intercept for a linear function passing through two points.

Returns:

Type Description
tuple[float, float]

A tuple containing the intercept and slope.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
@staticmethod
def _two_point_to_slope_intercept(x_1: float, y_1: float, x_2: float, y_2: float) -> tuple[float, float]:
    """Calculate the slope and intercept for a linear function passing through two points.

    Returns:
        A tuple containing the intercept and slope.
    """
    intercept = (x_2 * y_1 - x_1 * y_2) / (x_2 - x_1)
    slope = (y_2 - y_1) / (x_2 - x_1)
    return intercept, slope

calculate_amplitude(wavevector)

Calculate the amplitude for the given wavevector.

Parameters:

Name Type Description Default
wavevector ArrayLike

Scattering wavevector.

required

Returns:

Type Description
NDArray[float64]

The calculated amplitude array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
    """Calculate the amplitude for the given wavevector.

    Args:
        wavevector: Scattering wavevector.

    Returns:
        The calculated amplitude array.
    """
    wavevector = np.asarray(wavevector, dtype=np.float64)
    QR_outer = wavevector * self.radius_outer
    QR_inner = wavevector * self.radius_inner
    intercept, slope = self._two_point_to_slope_intercept(
        self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
    )
    amplitude_intercept = ConstantProfile(self.radius_inner, self.radius_outer, intercept).calculate_amplitude(
        wavevector
    )
    amplitude_gradient: NDArray[np.float64] = (2.0 - QR_outer**2) * np.cos(QR_outer) + 2.0 * QR_outer * np.sin(
        QR_outer
    )
    amplitude_gradient -= (2.0 - QR_inner**2) * np.cos(QR_inner) + 2.0 * QR_inner * np.sin(QR_inner)
    amplitude_gradient *= 4.0 * np.pi / wavevector**4 * slope
    return amplitude_intercept + amplitude_gradient

calculate_forward_amplitude()

Calculate the forward amplitude.

Returns:

Type Description
float

The calculated forward amplitude.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_forward_amplitude(self) -> float:
    """Calculate the forward amplitude.

    Returns:
        The calculated forward amplitude.
    """
    intercept, slope = self._two_point_to_slope_intercept(
        self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
    )
    forward_amplitude_intercept = ConstantProfile(
        self.radius_inner, self.radius_outer, intercept
    ).calculate_forward_amplitude()
    forward_amplitude_gradient = np.pi * (self.radius_outer**4 - self.radius_inner**4) * slope
    return forward_amplitude_intercept + forward_amplitude_gradient

calculate_second_moment()

Calculate the second moment.

Returns:

Type Description
float

The calculated second moment.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_second_moment(self) -> float:
    """Calculate the second moment.

    Returns:
        The calculated second moment.
    """
    intercept, slope = self._two_point_to_slope_intercept(
        self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
    )
    moment_intercept = ConstantProfile(self.radius_inner, self.radius_outer, intercept).calculate_second_moment()
    moment_slope = 2.0 / 3.0 * np.pi * (self.radius_outer**6 - self.radius_inner**6) * slope
    return moment_intercept + moment_slope

get_profile(distance)

Get the profile for the given distance from the origin.

Parameters:

Name Type Description Default
distance ArrayLike

Distance from the origin.

required

Returns:

Type Description
NDArray[float64]

The profile evaluated on the distance array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
    """Get the profile for the given distance from the origin.

    Args:
        distance: Distance from the origin.

    Returns:
        The profile evaluated on the distance array.
    """
    distance = np.asarray(distance, dtype=np.float64)
    distance_mask = (distance >= self.radius_inner) & (distance < self.radius_outer)
    intercept, slope = self._two_point_to_slope_intercept(
        self.radius_inner, self.contrast_inner, self.radius_outer, self.contrast_outer
    )
    profile = np.zeros_like(distance)
    profile[distance_mask] = intercept + slope * distance[distance_mask]
    return profile

Particle

Represents a particle composed of multiple layers.

Attributes:

Name Type Description
layers list

List of LayerProfile instances representing particle layers.

Methods:

Name Description
__init__

Initialize a particle with given layers.

calculate_amplitude

Calculate the amplitude for the given wavevector.

calculate_forward_amplitude

Calculate the forward amplitude.

calculate_form_factor

Calculate the form factor for the given wavevector.

get_profile

Get the profile for the given distance from the origin.

calculate_square_radius_of_gyration

Calculate the squared radius of the gyration.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class Particle:
    """
    Represents a particle composed of multiple layers.

    Attributes:
        layers (list): List of `LayerProfile` instances representing particle layers.

    Methods:
        __init__(layers):
            Initialize a particle with given layers.

        calculate_amplitude(wavevector):
            Calculate the amplitude for the given wavevector.

        calculate_forward_amplitude():
            Calculate the forward amplitude.

        calculate_form_factor(wavevector):
            Calculate the form factor for the given wavevector.

        get_profile(distance):
            Get the profile for the given distance from the origin.

        calculate_square_radius_of_gyration():
            Calculate the squared radius of the gyration.
    """

    def __init__(self, layers: list[LayerProfile]) -> None:
        """
        Initialize a particle with given layers.

        Args:
            layers: List of `LayerProfile` instances representing particle layers.
        """
        self.layers = layers

    def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
        """Calculate the amplitude for the given wavevector.

        Args:
            wavevector: Scattering wavevector.

        Returns:
            The calculated amplitude array.
        """
        wavevector = np.asarray(wavevector, dtype=np.float64)
        amplitude = np.zeros_like(wavevector)
        for layer in self.layers:
            amplitude += layer.calculate_amplitude(wavevector)
        return amplitude

    def calculate_forward_amplitude(self) -> float:
        """Calculate the forward amplitude.

        Returns:
            The calculated forward amplitude.
        """
        forward_amplitude = 0.0
        for layer in self.layers:
            forward_amplitude += layer.calculate_forward_amplitude()
        return forward_amplitude

    def calculate_form_factor(self, wavevector: ArrayLike) -> NDArray[np.float64]:
        """Calculate the form factor for the given wavevector.

        Args:
            wavevector: Scattering wavevector.

        Returns:
            The calculated form factor array.
        """
        wavevector = np.asarray(wavevector, dtype=np.float64)
        amplitude = self.calculate_amplitude(wavevector)
        forward_amplitude = self.calculate_forward_amplitude()
        return (amplitude / forward_amplitude) ** 2.0

    def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
        """Get the profile for the given distance from the origin.

        Args:
            distance: Distance from the origin.

        Returns:
            The profile evaluated on the distance array.
        """
        distance = np.asarray(distance, dtype=np.float64)
        profile = np.zeros_like(distance)
        for layer in self.layers:
            profile += layer.get_profile(distance)
        return profile

    def calculate_square_radius_of_gyration(self) -> float:
        """Calculate the squared radius of the gyration.
        Returns:
            The calculated squared radius of the gyration.
        """
        total_second_moment = 0.0
        for layer in self.layers:
            total_second_moment += layer.calculate_second_moment()
        square_radius_of_gyration = total_second_moment / self.calculate_forward_amplitude()
        return square_radius_of_gyration

__init__(layers)

Initialize a particle with given layers.

Parameters:

Name Type Description Default
layers list[LayerProfile]

List of LayerProfile instances representing particle layers.

required
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self, layers: list[LayerProfile]) -> None:
    """
    Initialize a particle with given layers.

    Args:
        layers: List of `LayerProfile` instances representing particle layers.
    """
    self.layers = layers

calculate_amplitude(wavevector)

Calculate the amplitude for the given wavevector.

Parameters:

Name Type Description Default
wavevector ArrayLike

Scattering wavevector.

required

Returns:

Type Description
NDArray[float64]

The calculated amplitude array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_amplitude(self, wavevector: ArrayLike) -> NDArray[np.float64]:
    """Calculate the amplitude for the given wavevector.

    Args:
        wavevector: Scattering wavevector.

    Returns:
        The calculated amplitude array.
    """
    wavevector = np.asarray(wavevector, dtype=np.float64)
    amplitude = np.zeros_like(wavevector)
    for layer in self.layers:
        amplitude += layer.calculate_amplitude(wavevector)
    return amplitude

calculate_form_factor(wavevector)

Calculate the form factor for the given wavevector.

Parameters:

Name Type Description Default
wavevector ArrayLike

Scattering wavevector.

required

Returns:

Type Description
NDArray[float64]

The calculated form factor array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_form_factor(self, wavevector: ArrayLike) -> NDArray[np.float64]:
    """Calculate the form factor for the given wavevector.

    Args:
        wavevector: Scattering wavevector.

    Returns:
        The calculated form factor array.
    """
    wavevector = np.asarray(wavevector, dtype=np.float64)
    amplitude = self.calculate_amplitude(wavevector)
    forward_amplitude = self.calculate_forward_amplitude()
    return (amplitude / forward_amplitude) ** 2.0

calculate_forward_amplitude()

Calculate the forward amplitude.

Returns:

Type Description
float

The calculated forward amplitude.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_forward_amplitude(self) -> float:
    """Calculate the forward amplitude.

    Returns:
        The calculated forward amplitude.
    """
    forward_amplitude = 0.0
    for layer in self.layers:
        forward_amplitude += layer.calculate_forward_amplitude()
    return forward_amplitude

calculate_square_radius_of_gyration()

Calculate the squared radius of the gyration. Returns: The calculated squared radius of the gyration.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def calculate_square_radius_of_gyration(self) -> float:
    """Calculate the squared radius of the gyration.
    Returns:
        The calculated squared radius of the gyration.
    """
    total_second_moment = 0.0
    for layer in self.layers:
        total_second_moment += layer.calculate_second_moment()
    square_radius_of_gyration = total_second_moment / self.calculate_forward_amplitude()
    return square_radius_of_gyration

get_profile(distance)

Get the profile for the given distance from the origin.

Parameters:

Name Type Description Default
distance ArrayLike

Distance from the origin.

required

Returns:

Type Description
NDArray[float64]

The profile evaluated on the distance array.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def get_profile(self, distance: ArrayLike) -> NDArray[np.float64]:
    """Get the profile for the given distance from the origin.

    Args:
        distance: Distance from the origin.

    Returns:
        The profile evaluated on the distance array.
    """
    distance = np.asarray(distance, dtype=np.float64)
    profile = np.zeros_like(distance)
    for layer in self.layers:
        profile += layer.get_profile(distance)
    return profile

ParticleBuilder

A builder class for constructing Particle instances.

This class facilitates the step-by-step construction of Particle objects composed of multiple layers, where each layer is represented by a LayerProfile instance. The builder ensures that layers are added sequentially, with checks for connectivity between layers and overlap prevention.

Attributes:

Name Type Description
layers list

List to store LayerProfile instances representing the layers of the particle.

Methods:

Name Description
__init__

Initializes an empty ParticleBuilder instance.

add_layer

Adds a layer to the particle being built. Ensures the new layer connects correctly to the previous layer and does not overlap with existing layers.

reset

Resets the builder instance, clearing all layers stored in the layers list.

get_particle

Constructs and returns a Particle instance using the layers added so far.

pop_particle

Constructs and returns a Particle instance using the layers added so far, then resets the builder instance to start building a new particle.

Usage

The typical usage involves creating a ParticleBuilder instance, adding layers using add_layer(), and finally retrieving the constructed particle using get_particle() or pop_particle().

Example

Construct a particle using ParticleBuilder:

>>> from mixscatter.scatteringmodel import ParticleBuilder, ConstantProfile
>>> layer1 = ConstantProfile(radius_inner=0.0, radius_outer=2.0, contrast=1.0)
>>> layer2 = ConstantProfile(radius_inner=2.0, radius_outer=5.0, contrast=0.5)
>>> builder = ParticleBuilder()
>>> particle = builder.add_layer(layer1).add_layer(layer2).get_particle()
Notes
  • The add_layer() method ensures that each added layer starts where the previous layer ends, preventing gaps or overlaps between layers.
  • The reset() method allows reusing the same builder instance to construct multiple particles.
  • The pop_particle() method is useful when constructing and retrieving particles in a single step, resetting the builder for the next particle construction.

Raises:

Type Description
RuntimeError

Raised by add_layer() method if the added layer does not connect correctly to the previous layer or if there is an overlap with existing layers.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class ParticleBuilder:
    """
    A builder class for constructing `Particle` instances.

    This class facilitates the step-by-step construction of `Particle` objects composed of multiple
    layers, where each layer is represented by a `LayerProfile` instance. The builder ensures that layers
    are added sequentially, with checks for connectivity between layers and overlap prevention.

    Attributes:
        layers (list): List to store `LayerProfile` instances representing the layers of the particle.

    Methods:
        __init__():
            Initializes an empty `ParticleBuilder` instance.

        add_layer(layer):
            Adds a layer to the particle being built. Ensures the new layer connects correctly to the
            previous layer and does not overlap with existing layers.

        reset():
            Resets the builder instance, clearing all layers stored in the layers list.

        get_particle():
            Constructs and returns a `Particle` instance using the layers added so far.

        pop_particle():
            Constructs and returns a `Particle` instance using the layers added so far, then resets the
            builder instance to start building a new particle.

    Usage:
        The typical usage involves creating a `ParticleBuilder` instance, adding layers using add_layer(),
        and finally retrieving the constructed particle using get_particle() or pop_particle().

    Example:
        Construct a particle using `ParticleBuilder`:

            >>> from mixscatter.scatteringmodel import ParticleBuilder, ConstantProfile
            >>> layer1 = ConstantProfile(radius_inner=0.0, radius_outer=2.0, contrast=1.0)
            >>> layer2 = ConstantProfile(radius_inner=2.0, radius_outer=5.0, contrast=0.5)
            >>> builder = ParticleBuilder()
            >>> particle = builder.add_layer(layer1).add_layer(layer2).get_particle()

    Notes:
        - The add_layer() method ensures that each added layer starts where the previous layer ends,
          preventing gaps or overlaps between layers.
        - The reset() method allows reusing the same builder instance to construct multiple particles.
        - The pop_particle() method is useful when constructing and retrieving particles in a single step,
          resetting the builder for the next particle construction.

    Raises:
        RuntimeError:
            Raised by add_layer() method if the added layer does not connect correctly to the
            previous layer or if there is an overlap with existing layers.
    """

    def __init__(self) -> None:
        """Initializes an empty `ParticleBuilder` instance."""
        self.layers: list[LayerProfile] = []

    def add_layer(self, layer: LayerProfile) -> Self:
        """Add a layer to the particle being built.

        Args:
            layer: `LayerProfile` instance to be added.

        Returns:
            The builder instance.

        Raises:
            RuntimeError: If the added layer does not connect to the previous layer or if layers overlap.
        """
        if self.layers:
            do_layers_connect = np.allclose(self.layers[-1].radius_outer, layer.radius_inner)
            if not do_layers_connect:
                raise RuntimeError(
                    f"Added layer with inner radius {layer.radius_inner} does not connect to previous layer's outer "
                    f"radius {self.layers[-1].radius_outer}."
                )
        self.layers.append(layer)
        return self

    def reset(self) -> None:
        """Resets the builder instance, clearing all layers stored in the layers list."""
        self.layers = []

    def get_particle(self) -> Particle:
        """
        Constructs and returns a `Particle` instance using the layers added so far.

        Returns:
            The constructed `Particle` instance.
        """
        return Particle(self.layers)

    def pop_particle(self) -> Particle:
        """
        Constructs and returns a `Particle` instance using the layers added so far,
        then resets the builder instance to start building a new particle.

        Returns:
            Particle: The constructed `Particle` instance.
        """
        particle = Particle(self.layers)
        self.reset()
        return particle

__init__()

Initializes an empty ParticleBuilder instance.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self) -> None:
    """Initializes an empty `ParticleBuilder` instance."""
    self.layers: list[LayerProfile] = []

add_layer(layer)

Add a layer to the particle being built.

Parameters:

Name Type Description Default
layer LayerProfile

LayerProfile instance to be added.

required

Returns:

Type Description
Self

The builder instance.

Raises:

Type Description
RuntimeError

If the added layer does not connect to the previous layer or if layers overlap.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def add_layer(self, layer: LayerProfile) -> Self:
    """Add a layer to the particle being built.

    Args:
        layer: `LayerProfile` instance to be added.

    Returns:
        The builder instance.

    Raises:
        RuntimeError: If the added layer does not connect to the previous layer or if layers overlap.
    """
    if self.layers:
        do_layers_connect = np.allclose(self.layers[-1].radius_outer, layer.radius_inner)
        if not do_layers_connect:
            raise RuntimeError(
                f"Added layer with inner radius {layer.radius_inner} does not connect to previous layer's outer "
                f"radius {self.layers[-1].radius_outer}."
            )
    self.layers.append(layer)
    return self

get_particle()

Constructs and returns a Particle instance using the layers added so far.

Returns:

Type Description
Particle

The constructed Particle instance.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def get_particle(self) -> Particle:
    """
    Constructs and returns a `Particle` instance using the layers added so far.

    Returns:
        The constructed `Particle` instance.
    """
    return Particle(self.layers)

pop_particle()

Constructs and returns a Particle instance using the layers added so far, then resets the builder instance to start building a new particle.

Returns:

Name Type Description
Particle Particle

The constructed Particle instance.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def pop_particle(self) -> Particle:
    """
    Constructs and returns a `Particle` instance using the layers added so far,
    then resets the builder instance to start building a new particle.

    Returns:
        Particle: The constructed `Particle` instance.
    """
    particle = Particle(self.layers)
    self.reset()
    return particle

reset()

Resets the builder instance, clearing all layers stored in the layers list.

Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def reset(self) -> None:
    """Resets the builder instance, clearing all layers stored in the layers list."""
    self.layers = []

ScatteringModel

Calculates scattering properties for a list of particles.

This class computes various scattering properties, including amplitudes, form factors, and averages over multiple particles. It handles both single-particle and multi-particle scattering scenarios.

Attributes:

Name Type Description
wavevector NDArray[float64]

Array of wavevector values at which scattering properties are computed.

mixture MixtureLike

Mixture object containing number fractions and radii of particle components.

particles list

List of Particle instances representing the particles in the scattering model.

Methods:

Name Description
__init__

Initializes a ScatteringModel instance with given wavevector, mixture, and particles.

Usage

The typical usage involves creating an instance of ScatteringModel with wavevector, mixture, and a list of particles. Methods such as amplitude(), average_form_factor(), etc., can then be called to compute specific scattering properties.

Example

Create a ScatteringModel instance from a list of particles:

>>> import numpy as np
>>> from mixscatter import Mixture, Particle, ScatteringModel
>>> wavevector = np.linspace(0.01, 1.0, 100)
>>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
>>> particle1 = Particle([ConstantProfile(0, 1.0, 1.0)])
>>> particle2 = Particle([ConstantProfile(0, 2.0, 0.5)])
>>> model = ScatteringModel(wavevector, mixture, [particle1, particle2])
>>> form_factor = model.average_form_factor
Notes
  • Scattering calculations are cached for efficiency.
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class ScatteringModel:
    # noinspection PyShadowingNames
    """
    Calculates scattering properties for a list of particles.

    This class computes various scattering properties, including amplitudes, form factors,
    and averages over multiple particles. It handles both single-particle and multi-particle
    scattering scenarios.

    Attributes:
        wavevector (NDArray[np.float64]): Array of wavevector values at which scattering properties are computed.
        mixture (MixtureLike): `Mixture` object containing number fractions and radii of particle components.
        particles (list): List of `Particle` instances representing the particles in the scattering model.

    Methods:
        __init__(wavevector, mixture, particles):
            Initializes a `ScatteringModel` instance with given wavevector, mixture, and particles.

    Usage:
        The typical usage involves creating an instance of `ScatteringModel` with wavevector, mixture,
        and a list of particles. Methods such as amplitude(), average_form_factor(), etc., can then
        be called to compute specific scattering properties.

    Example:
        Create a `ScatteringModel` instance from a list of particles:

            >>> import numpy as np
            >>> from mixscatter import Mixture, Particle, ScatteringModel
            >>> wavevector = np.linspace(0.01, 1.0, 100)
            >>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
            >>> particle1 = Particle([ConstantProfile(0, 1.0, 1.0)])
            >>> particle2 = Particle([ConstantProfile(0, 2.0, 0.5)])
            >>> model = ScatteringModel(wavevector, mixture, [particle1, particle2])
            >>> form_factor = model.average_form_factor

    Notes:
        - Scattering calculations are cached for efficiency.
    """

    def __init__(self, wavevector: ArrayLike, mixture: MixtureLike, particles: list[Particle]):
        """
        Initializes a `ScatteringModel` instance.

        Args:
            wavevector: Array of wavevector values at which scattering properties are computed.
            mixture: Mixture object containing number fractions and radii of particle components.
            particles: List of `Particle` instances representing the particles in the scattering model.
        """
        self.wavevector = np.asarray(wavevector, dtype=np.float64)
        self.mixture = mixture
        self.particles = particles

    @cached_property
    def amplitude(self) -> NDArray[np.float64]:
        """
        Calculates the scattering amplitude for each particle.

        Returns:
            Array of amplitudes for each particle at each wavevector point.
        """
        amplitude = np.empty((len(self.particles), len(self.wavevector)))
        for i, particle in enumerate(self.particles):
            amplitude[i] = particle.calculate_amplitude(self.wavevector)
        return amplitude

    @cached_property
    def forward_amplitude(self) -> NDArray[np.float64]:
        """
        Calculate the forward amplitude for each particle.

        Returns:
            An array of forward amplitudes for each particle.
        """
        forward_amplitude = np.empty(len(self.particles))
        for i, particle in enumerate(self.particles):
            forward_amplitude[i] = particle.calculate_forward_amplitude()
        return forward_amplitude

    @cached_property
    def single_form_factor(self) -> NDArray[np.float64]:
        """
        Computes the normalized form factors of the single species particles.

        Returns:
            Form factors of the single species particles.
        """
        return self.amplitude**2 / self.forward_amplitude[:, np.newaxis] ** 2

    @cached_property
    def average_square_amplitude(self) -> NDArray[np.float64]:
        """The sum of the squared scattering amplitudes, weighted by the number fraction.

        Returns:
            The average squared scattering amplitude.
        """
        average_square_amplitude: NDArray[np.float64] = np.asanyarray(
            np.sum(self.mixture.number_fraction[:, np.newaxis] * self.amplitude**2, axis=0), dtype=np.float64
        )
        return average_square_amplitude

    @cached_property
    def average_square_forward_amplitude(self) -> float:
        """The sum of the squared forward scattering amplitudes, weighted by the number fraction.

        Returns:
            The average squared forward scattering amplitude.
        """
        average_square_forward_amplitude = float(
            np.sum(self.mixture.number_fraction * self.forward_amplitude**2, axis=0)
        )
        return average_square_forward_amplitude

    @cached_property
    def average_form_factor(self) -> NDArray[np.float64]:
        """The average squared scattering amplitude, normalized by the average forward scattering amplitude.

        Returns:
            The average form factor.
        """

        return np.asanyarray(self.average_square_amplitude / self.average_square_forward_amplitude, dtype=np.float64)

    @cached_property
    def square_radius_of_gyration(self) -> NDArray[np.float64]:
        """
        Calculate the radius of gyration for each particle.

        Returns:
            An array containing the square radius of gyration for each particle.
        """
        square_radius_of_gyration = np.empty((len(self.particles)))
        for i, particle in enumerate(self.particles):
            square_radius_of_gyration[i] = particle.calculate_square_radius_of_gyration()
        return square_radius_of_gyration

    @cached_property
    def average_square_radius_of_gyration(self) -> float:
        """
        Computes the average, apparent radius of gyration of the system. The apparent radius of
        gyration determines the initial slope of the average form factor.

        Returns:
             The average radius of gyration of the system.
        """
        average_square_radius_of_gyration: float = float(
            np.sum(self.mixture.number_fraction * self.forward_amplitude**2 * self.square_radius_of_gyration, axis=0)
            / self.average_square_forward_amplitude
        )
        return average_square_radius_of_gyration

amplitude cached property

Calculates the scattering amplitude for each particle.

Returns:

Type Description
NDArray[float64]

Array of amplitudes for each particle at each wavevector point.

average_form_factor cached property

The average squared scattering amplitude, normalized by the average forward scattering amplitude.

Returns:

Type Description
NDArray[float64]

The average form factor.

average_square_amplitude cached property

The sum of the squared scattering amplitudes, weighted by the number fraction.

Returns:

Type Description
NDArray[float64]

The average squared scattering amplitude.

average_square_forward_amplitude cached property

The sum of the squared forward scattering amplitudes, weighted by the number fraction.

Returns:

Type Description
float

The average squared forward scattering amplitude.

average_square_radius_of_gyration cached property

Computes the average, apparent radius of gyration of the system. The apparent radius of gyration determines the initial slope of the average form factor.

Returns:

Type Description
float

The average radius of gyration of the system.

forward_amplitude cached property

Calculate the forward amplitude for each particle.

Returns:

Type Description
NDArray[float64]

An array of forward amplitudes for each particle.

single_form_factor cached property

Computes the normalized form factors of the single species particles.

Returns:

Type Description
NDArray[float64]

Form factors of the single species particles.

square_radius_of_gyration cached property

Calculate the radius of gyration for each particle.

Returns:

Type Description
NDArray[float64]

An array containing the square radius of gyration for each particle.

__init__(wavevector, mixture, particles)

Initializes a ScatteringModel instance.

Parameters:

Name Type Description Default
wavevector ArrayLike

Array of wavevector values at which scattering properties are computed.

required
mixture MixtureLike

Mixture object containing number fractions and radii of particle components.

required
particles list[Particle]

List of Particle instances representing the particles in the scattering model.

required
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self, wavevector: ArrayLike, mixture: MixtureLike, particles: list[Particle]):
    """
    Initializes a `ScatteringModel` instance.

    Args:
        wavevector: Array of wavevector values at which scattering properties are computed.
        mixture: Mixture object containing number fractions and radii of particle components.
        particles: List of `Particle` instances representing the particles in the scattering model.
    """
    self.wavevector = np.asarray(wavevector, dtype=np.float64)
    self.mixture = mixture
    self.particles = particles

SimpleCoreShell

Bases: ScatteringModel

A convenience class for creating a scattering model of core-shell particles with a constant core-to-shell ratio.

This class simplifies the creation of a scattering model where particles are represented by core-shell structures with varying contrasts for the core and shell layers.

Attributes:

Name Type Description
wavevector NDArray[float64]

Array of wavevector values at which scattering properties are computed.

mixture MixtureLike

Mixture object containing number fractions and radii of particle components.

particles list

List of Particle instances representing the particles in the scattering model.

Methods:

Name Description
__init__

Initializes a SimpleCoreShell instance with given wavevector, mixture, and contrast parameters.

Usage

The typical usage involves creating an instance of SimpleCoreShell with specific wavevector, mixture, core-shell ratio, and contrast parameters, then using its inherited methods to compute scattering properties.

Example

Initialize a SimpleCoreShell instance with wavevector, mixture, and contrast parameters

>>> import numpy as np
>>> from mixscatter import Mixture, SimpleCoreShell
>>> wavevector = np.linspace(0.01, 1.0, 100)
>>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
>>> model = SimpleCoreShell(
...     wavevector, mixture, core_to_total_ratio=0.5, core_contrast=1.0, shell_contrast=0.5
... )
>>> form_factor = model.average_form_factor
Notes
  • The particle radii are inferred from the provided Mixture instance.
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class SimpleCoreShell(ScatteringModel):
    # noinspection PyShadowingNames
    """
    A convenience class for creating a scattering model of core-shell particles with a constant core-to-shell ratio.

    This class simplifies the creation of a scattering model where particles are represented
    by core-shell structures with varying contrasts for the core and shell layers.

    Attributes:
        wavevector (NDArray[np.float64]): Array of wavevector values at which scattering properties are computed.
        mixture (MixtureLike): `Mixture` object containing number fractions and radii of particle components.
        particles (list): List of `Particle` instances representing the particles in the scattering model.

    Methods:
        __init__(wavevector, mixture, core_to_total_ratio, core_contrast, shell_contrast):
            Initializes a `SimpleCoreShell` instance with given wavevector, mixture, and contrast parameters.

    Usage:
        The typical usage involves creating an instance of `SimpleCoreShell` with specific wavevector, mixture,
        core-shell ratio, and contrast parameters, then using its inherited methods to compute scattering
        properties.

    Example:
        Initialize a `SimpleCoreShell` instance with wavevector, mixture, and contrast parameters

            >>> import numpy as np
            >>> from mixscatter import Mixture, SimpleCoreShell
            >>> wavevector = np.linspace(0.01, 1.0, 100)
            >>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
            >>> model = SimpleCoreShell(
            ...     wavevector, mixture, core_to_total_ratio=0.5, core_contrast=1.0, shell_contrast=0.5
            ... )
            >>> form_factor = model.average_form_factor

    Notes:
        - The particle radii are inferred from the provided `Mixture` instance.
    """

    def __init__(
        self,
        wavevector: ArrayLike,
        mixture: MixtureLike,
        core_to_total_ratio: float,
        core_contrast: float,
        shell_contrast: float,
    ) -> None:
        """
        Initializes a `SimpleCoreShell` instance.

        Args:
            wavevector: Array of wavevector values at which scattering properties are computed.
            mixture: `Mixture` object containing number fractions and radii of particle components.
            core_to_total_ratio: Ratio of core radius to total particle radius.
            core_contrast: Scattering contrast of the core.
            shell_contrast: Scattering contrast of the shell.
        """
        particles = []
        particle_builder = ParticleBuilder()
        for total_radius in mixture.radius:
            core_radius = total_radius * core_to_total_ratio
            particle = (
                particle_builder.add_layer(ConstantProfile(0, core_radius, core_contrast))
                .add_layer(ConstantProfile(core_radius, total_radius, shell_contrast))
                .pop_particle()
            )
            particles.append(particle)
        super().__init__(wavevector, mixture, particles)

__init__(wavevector, mixture, core_to_total_ratio, core_contrast, shell_contrast)

Initializes a SimpleCoreShell instance.

Parameters:

Name Type Description Default
wavevector ArrayLike

Array of wavevector values at which scattering properties are computed.

required
mixture MixtureLike

Mixture object containing number fractions and radii of particle components.

required
core_to_total_ratio float

Ratio of core radius to total particle radius.

required
core_contrast float

Scattering contrast of the core.

required
shell_contrast float

Scattering contrast of the shell.

required
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(
    self,
    wavevector: ArrayLike,
    mixture: MixtureLike,
    core_to_total_ratio: float,
    core_contrast: float,
    shell_contrast: float,
) -> None:
    """
    Initializes a `SimpleCoreShell` instance.

    Args:
        wavevector: Array of wavevector values at which scattering properties are computed.
        mixture: `Mixture` object containing number fractions and radii of particle components.
        core_to_total_ratio: Ratio of core radius to total particle radius.
        core_contrast: Scattering contrast of the core.
        shell_contrast: Scattering contrast of the shell.
    """
    particles = []
    particle_builder = ParticleBuilder()
    for total_radius in mixture.radius:
        core_radius = total_radius * core_to_total_ratio
        particle = (
            particle_builder.add_layer(ConstantProfile(0, core_radius, core_contrast))
            .add_layer(ConstantProfile(core_radius, total_radius, shell_contrast))
            .pop_particle()
        )
        particles.append(particle)
    super().__init__(wavevector, mixture, particles)

SimpleGradient

Bases: ScatteringModel

A convenience class for creating a scattering model of particles with a linear contrast gradient.

This class simplifies the creation of a scattering model representing particles with a scattering contrast varying linearly from the contrast at the particle center to the contrast at the boundary.

Methods:

Name Description
__init__

Initializes a SimpleGradient instance with given wavevector, mixture, and center and boundary contrast.

Usage

The typical usage involves creating an instance of SimpleGradient with specific wavevector, mixture, and contrast profile, then using its inherited methods to compute scattering properties.

Example

Initialize a SimpleGradient instance with wavevector, mixture, and contrast parameters:

>>> import numpy as np
>>> from mixscatter import Mixture
>>> wavevector = np.linspace(0.01, 1.0, 100)
>>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
>>> model = SimpleGradient(wavevector, mixture, center_contrast=1.0, boundary_contrast=0.5)
>>> form_factor = model.average_form_factor
Notes
  • The particle radii are inferred from the provided Mixture instance.
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class SimpleGradient(ScatteringModel):
    # noinspection PyShadowingNames
    """
    A convenience class for creating a scattering model of particles with a linear contrast gradient.

    This class simplifies the creation of a scattering model representing particles with a scattering contrast
    varying linearly from the contrast at the particle center to the contrast at the boundary.

    Methods:
        __init__(wavevector, mixture, center_contrast, boundary_contrast):
            Initializes a `SimpleGradient` instance with given wavevector, mixture, and center and boundary
            contrast.

    Usage:
        The typical usage involves creating an instance of `SimpleGradient` with specific wavevector, mixture,
        and contrast profile, then using its inherited methods to compute scattering properties.

    Example:
        Initialize a `SimpleGradient` instance with wavevector, mixture, and contrast parameters:

            >>> import numpy as np
            >>> from mixscatter import Mixture
            >>> wavevector = np.linspace(0.01, 1.0, 100)
            >>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
            >>> model = SimpleGradient(wavevector, mixture, center_contrast=1.0, boundary_contrast=0.5)
            >>> form_factor = model.average_form_factor

    Notes:
        - The particle radii are inferred from the provided `Mixture` instance.
    """

    def __init__(
        self,
        wavevector: ArrayLike,
        mixture: MixtureLike,
        center_contrast: float,
        boundary_contrast: float,
    ) -> None:
        particles = []
        particle_builder = ParticleBuilder()
        for radius in mixture.radius:
            particle = particle_builder.add_layer(
                LinearProfile(0, radius, center_contrast, boundary_contrast)
            ).pop_particle()
            particles.append(particle)
        super().__init__(wavevector, mixture, particles)

SimpleSphere

Bases: ScatteringModel

A convenience class for creating a scattering model of homogeneously scattering spheres.

This class simplifies the creation of a scattering model where particles are represented by homogeneously scattering spheres with a common, constant contrast.

Attributes:

Name Type Description
wavevector NDArray[float64]

Array of wavevector values at which scattering properties are computed.

mixture MixtureLike

Mixture object containing number fractions and radii of particle components.

particles list

List of Particle instances representing the particles in the scattering model.

Methods:

Name Description
__init__

Initializes a SimpleSphere instance with given wavevector, mixture, and contrast.

Usage

The typical usage involves creating an instance of SimpleSphere with specific wavevector, mixture, and contrast parameters, then using its inherited methods to compute scattering properties.

Example

Initialize a SimpleSphere instance with wavevector, mixture, and contrast parameters:

>>> import numpy as np
>>> from mixscatter import Mixture, SimpleSphere
>>> wavevector = np.linspace(0.01, 1.0, 100)
>>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
>>> model = SimpleSphere(wavevector, mixture, contrast=1.0)
>>> form_factor = model.average_form_factor
Notes
  • The particle radii are inferred from the provided Mixture instance.
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
class SimpleSphere(ScatteringModel):
    # noinspection PyShadowingNames
    """
    A convenience class for creating a scattering model of homogeneously scattering spheres.

    This class simplifies the creation of a scattering model where particles are represented
    by homogeneously scattering spheres with a common, constant contrast.

    Attributes:
        wavevector (NDArray[np.float64]): Array of wavevector values at which scattering properties are computed.
        mixture (MixtureLike): `Mixture` object containing number fractions and radii of particle components.
        particles (list): List of `Particle` instances representing the particles in the scattering model.

    Methods:
        __init__(wavevector, mixture, contrast):
            Initializes a `SimpleSphere` instance with given wavevector, mixture, and contrast.

    Usage:
        The typical usage involves creating an instance of `SimpleSphere` with specific wavevector,
        mixture, and contrast parameters, then using its inherited methods to compute scattering properties.

    Example:
        Initialize a `SimpleSphere` instance with wavevector, mixture, and contrast parameters:

            >>> import numpy as np
            >>> from mixscatter import Mixture, SimpleSphere
            >>> wavevector = np.linspace(0.01, 1.0, 100)
            >>> mixture = Mixture(number_fraction=[0.5, 0.5], radius=[1.0, 2.0])
            >>> model = SimpleSphere(wavevector, mixture, contrast=1.0)
            >>> form_factor = model.average_form_factor

    Notes:
        - The particle radii are inferred from the provided `Mixture` instance.
    """

    def __init__(self, wavevector: ArrayLike, mixture: MixtureLike, contrast: float) -> None:
        """
        Initializes a `SimpleSphere` instance.

        Args:
            wavevector: Array of wavevector values at which scattering properties are computed.
            mixture: `Mixture` object containing number fractions and radii of particle components.
            contrast: Scattering contrast of the spheres.
        """
        particles = []
        particle_builder = ParticleBuilder()
        for radius in mixture.radius:
            particle = particle_builder.add_layer(ConstantProfile(0, radius, contrast)).pop_particle()
            particles.append(particle)
        super().__init__(wavevector, mixture, particles)

__init__(wavevector, mixture, contrast)

Initializes a SimpleSphere instance.

Parameters:

Name Type Description Default
wavevector ArrayLike

Array of wavevector values at which scattering properties are computed.

required
mixture MixtureLike

Mixture object containing number fractions and radii of particle components.

required
contrast float

Scattering contrast of the spheres.

required
Source code in src/mixscatter/scatteringmodel/scatteringmodel.py
def __init__(self, wavevector: ArrayLike, mixture: MixtureLike, contrast: float) -> None:
    """
    Initializes a `SimpleSphere` instance.

    Args:
        wavevector: Array of wavevector values at which scattering properties are computed.
        mixture: `Mixture` object containing number fractions and radii of particle components.
        contrast: Scattering contrast of the spheres.
    """
    particles = []
    particle_builder = ParticleBuilder()
    for radius in mixture.radius:
        particle = particle_builder.add_layer(ConstantProfile(0, radius, contrast)).pop_particle()
        particles.append(particle)
    super().__init__(wavevector, mixture, particles)