Example #1
0
def test_SmoothData1D():
    """ test smoothing """
    x = np.random.uniform(0, 1, 128)
    xs = np.linspace(0, 1, 16)[1:-1]

    s = SmoothData1D(x, np.ones_like(x))
    np.testing.assert_allclose(s(xs), 1)

    s = SmoothData1D(x, x)
    np.testing.assert_allclose(s(xs), xs, atol=0.1)

    s = SmoothData1D(x, np.sin(x))
    np.testing.assert_allclose(s(xs), np.sin(xs), atol=0.1)
Example #2
0
def test_SmoothData1D():
    """test smoothing"""
    x = np.random.uniform(0, 1, 128)
    xs = np.linspace(0, 1, 16)[1:-1]

    s = SmoothData1D(x, np.ones_like(x))
    np.testing.assert_allclose(s(xs), 1)

    s = SmoothData1D(x, x)
    np.testing.assert_allclose(s(xs), xs, atol=0.1)

    s = SmoothData1D(x, np.sin(x))
    np.testing.assert_allclose(s(xs), np.sin(xs), atol=0.1)

    assert -0.1 not in s
    assert x.min() in s
    assert 0.5 in s
    assert x.max() in s
    assert 1.1 not in s

    x = np.arange(3)
    y = [0, 1, np.nan]
    s = SmoothData1D(x, y)
    assert s(0.5) == pytest.approx(0.5)
Example #3
0
def get_length_scale(
    scalar_field: ScalarField,
    method: str = "structure_factor_maximum",
    full_output: bool = False,
    smoothing: Optional[float] = None,
) -> Union[float, Tuple[float, Any]]:
    """Calculates a length scale associated with a phase field

    Args:
        scalar_field (:class:`~pde.fields.ScalarField`):
            The scalar_field being analyzed
        method (str):
            A string determining which method is used to calculate the length
            scale.Valid options are `structure_factor_maximum` (numerically
            determine the maximum in the structure factor) and
            `structure_factor_mean` (calculate the mean of the structure
            factor).
        full_output (bool):
            Flag determining whether additional data is returned. The format of
            the returned data depends on the method.
        smoothing (float, optional):
            Length scale that determines the smoothing of the radially averaged
            structure factor. If `None` it is automatically determined from the
            typical discretization of the underlying grid. This parameter is
            only used if `method = 'structure_factor_maximum'`

    Returns:
        float: The determine length scale

        If `full_output = True`, a tuple with the length scale and an additional
        object with further information is returned.
    """
    logger = logging.getLogger(__name__)

    if method == "structure_factor_mean" or method == "structure_factor_average":
        # calculate the structure factor
        k_mag, sf = get_structure_factor(scalar_field)
        length_scale = np.sum(sf) / np.sum(k_mag * sf)

        if full_output:
            return length_scale, sf

    elif method == "structure_factor_maximum" or method == "structure_factor_peak":
        # calculate the structure factor
        k_mag, sf = get_structure_factor(scalar_field, smoothing=None)

        # smooth the structure factor
        if smoothing is None:
            smoothing = 0.01 * scalar_field.grid.typical_discretization
        sf_smooth = SmoothData1D(k_mag, sf, sigma=smoothing)

        # find the maximum
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")

            max_est = k_mag[np.argmax(sf)]
            bracket = np.array([0.2, 1, 5]) * max_est
            logger.debug(f"Search maximum of structure factor in interval {bracket}")
            try:
                result = optimize.minimize_scalar(
                    lambda x: -sf_smooth(x), bracket=bracket
                )
            except Exception:
                logger.exception("Could not determine maximal structure factor")
                length_scale = np.nan
            else:
                if not result.success:
                    logger.warning(
                        "Maximization of structure factor resulted in the following "
                        f"message: {result.message}"
                    )
                length_scale = 1 / result.x

        if full_output:
            return length_scale, sf_smooth

    else:
        raise ValueError(
            f"Method {method} is not defined. Valid values are `structure_factor_mean` "
            "and `structure_factor_maximum`"
        )

    # return only the length scale with out any additional information
    return length_scale  # type: ignore
Example #4
0
def get_structure_factor(
    scalar_field: ScalarField,
    smoothing: Union[None, float, str] = "auto",
    wave_numbers: Union[Sequence[float], str] = "auto",
    add_zero: bool = False,
) -> Tuple[np.ndarray, np.ndarray]:
    """Calculates the structure factor associated with a field

    Here, the structure factor is basically the power spectral density of the
    field `scalar_field` normalized so that re-gridding or rescaling the field
    does not change the result.

    Args:
        scalar_field (:class:`~pde.fields.ScalarField`):
            The scalar_field being analyzed
        smoothing (float, optional):
            Length scale that determines the smoothing of the radially averaged
            structure factor. If omitted, the full data about the discretized
            structure factor is returned. The special value `auto` calculates
            a value automatically.
        wave_numbers (list of floats, optional):
            The magnitude of the wave vectors at which the structure factor is
            evaluated. This only applies when smoothing is used. If `auto`, the
            wave numbers are determined automatically.
        add_zero (bool):
            Determines whether the value at k=0 (defined to be 1) should also be
            returned.

    Returns:
        (numpy.ndarray, numpy.ndarray): Two arrays giving the wave numbers and
        the associated structure factor
    """
    logger = logging.getLogger(__name__)

    if not isinstance(scalar_field, ScalarField):
        raise TypeError(
            "Length scales can only be calculated for scalar "
            f"fields, not {scalar_field.__class__.__name__}"
        )

    grid = scalar_field.grid
    if not isinstance(grid, CartesianGridBase):
        raise NotImplementedError(
            "Structure factor can currently only be calculated for Cartesian grids"
        )
    if not all(grid.periodic):
        logger.warning(
            "Structure factor calculation assumes periodic boundary "
            "conditions, but not all grid dimensions are periodic"
        )

    # do the n-dimensional Fourier transform and calculate the structure factor
    f1 = np_fftn(scalar_field.data, norm="ortho").flat[1:]
    flat_data = scalar_field.data.flat
    sf = np.abs(f1) ** 2 / np.dot(flat_data, flat_data)

    # an alternative calculation of the structure factor is
    #    f2 = np_ifftn(scalar_field.data, norm='ortho').flat[1:]
    #    sf = (f1 * f2).real
    #    sf /= (scalar_field.data**2).sum()
    # but since this involves two FFT, it is probably slower

    # determine the (squared) components of the wave vectors
    k2s = [
        np.fft.fftfreq(grid.shape[i], d=grid.discretization[i]) ** 2
        for i in range(grid.dim)
    ]
    # calculate the magnitude
    k_mag = np.sqrt(reduce(np.add.outer, k2s)).flat[1:]

    no_wavenumbers = wave_numbers is None or (
        isinstance(wave_numbers, str) and wave_numbers == "auto"
    )

    if smoothing is not None and smoothing != "none":
        # construct the smoothed function of the structure factor
        if smoothing == "auto":
            smoothing = k_mag.max() / 128
        smoothing = float(smoothing)  # type: ignore
        sf_smooth = SmoothData1D(k_mag, sf, sigma=smoothing)

        if no_wavenumbers:
            # determine the wave numbers at which to evaluate it
            k_min = 2 / grid.cuboid.size.max()
            k_max = k_mag.max()
            k_mag = np.linspace(k_min, k_max, 128)

        else:
            k_mag = np.array(wave_numbers)

        # obtain the smoothed values at these points
        sf = sf_smooth(k_mag)

    elif not no_wavenumbers:
        logger.warning(
            "Argument `wave_numbers` is only used when `smoothing` is enabled."
        )

    if add_zero:
        sf = np.r_[1, sf]
        k_mag = np.r_[0, k_mag]

    return k_mag, sf