1

I'm trying to fit a power-law to data which is in the double log scale. Therefore I've used the curve_fit(...) function from the scipy.optimize package. To run the function I've implemented the following piece of code COR_coef[i] = curve_fit(lambda x, m: c * x ** m, x, COR_IFG[:, i])[0][0], to the best of my knowledge the curve_fit(...) should now correctly fit a power-law (being a straight line) to my data. However, for some reason, I just do not seem to get the fit right. See the attached picture for the data and its fit.

Blue line is the data the power-law needs to be fitted to, the orange line is the fitted power-law.


Some more context with regards to the minimum reproducible example (see below):

  • The code generates random noise for simulation purposes, this is done in the white_noise(...)
  • This random noise is than misaligned (in a for-loop with different fractions of misalignment according to the variable fractions_to_shift so the development of the power-law can be studied) and subtracted from the original noise to gain a residual signal
  • The residual signal is the signal the power-law is fitted to
  • The curve_fit(...) is applied in the sim_powerlaw_coefficient(...) function
  • I am aware of the fact that my residual signal shows some artifacts when the misalignment gets larger, unfortunately I don't know how to get rid of these artifacts.

MINIMUM REPRODUCIBLE EXAMPLE

import matplotlib.pyplot as plt
import numpy as np
import numpy.fft as fft
import numpy.random as rnd
from scipy.optimize import curve_fit

plt.style.use('seaborn-darkgrid')

rnd.seed(100)  # to select a random seed for creating the "random" noise
grad = -5 / 3. # slope to use for every function
c = 1 # base parameter for the powerlaw
ylim = [1e-7, 30] # range for the double log plots of the powerfrequency domains
values_to_shift = [0, 2**-11, 2**-10, 2**-9, 2**-8, 2**-7, 2**-6, 2**-5, 2**-4, 2**-3, 2**-2, 2**-1, 2**0] # fractions of missalignment

def white_noise(n: int, N: int):
    """
    - Creates a data set of white noise with size n, N;
    - Filters this dataset with the corresponding slope;
        This slope is usually equal to -5/3 or -2/3
    - Makes sure the slope is equal to the requested slope in the double log scale.

    @param n: size of random array
    @param N: number of random arrays
    @param slope: slope of the gradient
    @return: white_noise, filtered white_noise and the original signal
    """

    m = grad
    x = np.linspace(1, n, n // 2)
    slope_loglog = c * x ** m

    whitenoise = rnd.randn(n // 2, N) + 1j * rnd.randn(n // 2, N)
    whitenoise[0, :] = 0  # zero-mean noise
    whitenoise_filtered = whitenoise * slope_loglog[:, np.newaxis]

    whitenoise = 2 * np.pi * np.concatenate((whitenoise, whitenoise[0:1, :], np.conj(whitenoise[-1:0:-1, :])), axis=0)
    whitenoise_filtered = 2 * np.pi * np.concatenate(
        (whitenoise_filtered, whitenoise_filtered[0:1, :], np.conj(whitenoise_filtered[-1:0:-1, :])), axis=0)

    whitenoise_signal = fft.ifft(whitenoise_filtered, axis=0)
    whitenoise_signal = np.real_if_close(whitenoise_signal)
    if np.iscomplex(whitenoise_signal).any():
        print('Warning! whitenoise_signal is complex-valued!')
    whitenoise_retransformed = fft.fft(whitenoise_signal, axis=0)

    return whitenoise, whitenoise_filtered, whitenoise_signal, whitenoise_retransformed, slope_loglog


def sim_powerlaw_coefficient(n: int, N: int, show_powerlaw=0):
    """
    @param n: Number of values in the IFG
    @param N: Number of IFG's
    @return: Returns the coefficient after subtraction of two IFG's
    """

    master = white_noise(n, N)
    slave = white_noise(n, N)
    x = np.linspace(1, n, n // 2)

    signal_IFG = master[2] - slave[2]
    noise_IFG = np.abs(fft.fft(signal_IFG, axis=0))[0:n // 2, :]

    for k in range(len(values_to_shift)):
        shift = np.int(np.round(values_to_shift[k] * n, 0))
        inp = signal_IFG.copy()

        # the weather model is a shifted copy of the actual signal, to better understand the errors that are introduced.
        weather_model = np.roll(inp, shift, axis=0)
        WM_IFG = np.abs(fft.fft(weather_model, axis=0)[0:n // 2, :])

        signal_corrected = signal_IFG - weather_model
        COR_IFG = np.abs(fft.fft(signal_corrected, axis=0)[0:n // 2, :])

        COR_coef = np.zeros(N)

        for i in range(N):
            COR_coef[i] = curve_fit(lambda x, m: c * x ** m, x, COR_IFG[:, i])[0][0]

        plt.figure(figsize=(15, 10))

        plt.title('Corrected IFG (combined - weather model)')
        plt.loglog(COR_IFG, label='Corrected IFG')
        plt.ylim(ylim)
        plt.xlabel('log(k)')
        plt.ylabel('log(P)')

        plt.loglog(c * x ** COR_coef.mean(), '-.', label=f'COR powerlaw coef:{COR_coef.mean()}')

        plt.legend(loc=0)
        plt.tight_layout()

sim_powerlaw_coefficient(8192, 1, show_powerlaw=1)

Kars
  • 39
  • 7

0 Answers0