Estimation of the Hurst function of a multifractional Brownian motion#

Code author: Frédéric Richard <frederic.richard_at_univ-amu.fr>

The multifractional Brownian motion is a random process whose regularity varies in time. For this process, this regularity is determined by a functional parameter called the Hurst function. This function maps time position into the interval (0, 1). The larger the value, the smoother the process at a position. In this example, we use varprox to estimate the Hurst function.

To define the objective function, we use a least square criterion which compares the local quadratic variations of an observed process to the theoretical ones of a multifractional Brownian motion. We use a TV regularization to stabilize the estimation and evaluate its effect.

Note

This example requires the installation of the PyAFBF_.

[1]:
from afbf import process
from numpy.random import default_rng, seed
from numpy import zeros, std, arange, power, mean, maximum, minimum, log, array
from numpy import concatenate, ones, inf
from scipy.optimize import lsq_linear
from varprox import Minimize, Parameters
from varprox.models.model_mfbm import Ffun, DFfun
from matplotlib import pyplot as plt


def Simulate_MFBM(H, seed_n=1):
    """Simulate the multi-fractional Brownian motion of Hurst function H.

    :param ndarray H: The hurst function of the process.
    :type H: :ref:`ndarray`

    :param seed_n: A seed number.
    :type seed_n: int.

    :returns: a simulation of the process.
    """
    N = H.size

    fbm = process()
    y = zeros((N,))
    if min(H) != max(H):
        # Simulation of a multifractional Brownian motion.
        for j in range(N):
            seed(seed_n)
            fbm.param = H[j]
            fbm.Simulate(N)
            y[j] = 10 * fbm.y[j, 0] / std(fbm.y)
    else:
        # Simulation of a fractional Brownian motion.
        seed(seed_n)
        fbm.param = H[0]
        fbm.Simulate(N)
        y = fbm.y

    return y


def Estimate_LocalSemiVariograms(y, scales, w_size, w_step, order=0):
    """Compute the local semi-variogram of the process.

    :param y: A process
    :type H: :ref:`ndarray`
    :param scales: Scales at which the semi-variogram is to be computed.
    :type H: :ref:`ndarray`
    :param w_size: Size of the window where the semi-variogram is computed.
    :type w_size: int
    :param w_step: Step between two successive positions of computations.
    :type w_step: int
    :param order: Order of the increments. The default is 0.
    :type order: int

    :returns: Semi-variograms at each position (row) and scale (column).
    """

    N = y.size
    order += 1
    Nr = N - order * max(scales) - w_size

    if Nr < 0:
        raise Exception("Decrease max of scales or w_size.")

    v = zeros((Nr // w_step + 1, scales.size))
    for j in range(scales.size):
        # Increments
        scale = scales[j]
        increm = zeros(y.shape)
        increm[:] = y[:]
        for o in range(order):
            increm = increm[:-scale] - increm[scale:]
        increm = power(increm[:-scale] - increm[scale:], 2)
        # Local semi-variogram.
        w_ind = 0
        for w in range(0, Nr, w_step):
            v[w_ind, j] = 0.5 * mean(increm[w:w + w_size])
            w_ind += 1

    return v


def Estimate_HurstFunction(scales, v):
    """Estimate the Hurst function using linear regressions.

    :param scales: Scales at which the semi-variogram was computed.
    :type H: :ref:`ndarray`
    :param v: Empirical semi-variogram.
    :type v: :ref:`ndarray`

    :return: Variance factors, Hurst function.
    :rtype: :ref:`ndarray`

    """
    N = v.shape[0]  # Number of positions.
    P = v.shape[1]  # Number of scales.
    v = log(maximum(v, 1e-310))
    scales = 2 * log(scales).reshape((P, 1))
    X = concatenate((scales, ones((P, 1))), axis=1)
    lb = array([0, - inf])
    ub = array([1, inf])

    H = zeros((N,))
    c = zeros((N,))
    for j in range(N):
        # Estimate the Hurst index at the nth position.
        pb = lsq_linear(X, v[j, :], bounds=(lb, ub))
        H[j] = pb.x[0]
        c[j] = pb.x[1]

    return H, c


rng = default_rng()
seed(15)

# Experiment parameters
N = 1000  # Size of the observed process.
order = 1  # Order of the quadratic variations to analyze the process.
scales = array([1, 5, 10])  # scales at which analyze the process.
w_size = 400  # Size of the local window where the process is analyzed.
w_step = 20  # Displacement step of the window.
H1 = 0.2  # Minimal Hurst value.
H2 = 0.3  # Maximal Hurst value.
mfbm = True  # Set to True for experiment on multifractional Brownian field.

#: 1. Simulation.
#: Create the Hurst function.
if mfbm:
    T = arange(stop=N, step=2)
    N = N - 1
    T = power(T / N, 2)
    H = (1 - T) * H1 + T * H2
    H = concatenate((H[::-1], H[1:]))
else:
    H = 0.3 * ones(H.shape)
#: Simulate a mfbm of Hurst function H.
seed_n = 1
y = Simulate_MFBM(H, seed_n)
# Estimate the local semi-variogram of y.
v = Estimate_LocalSemiVariograms(y, scales, w_size, w_step, order)

#: 2. Estimations.
#: 2.1. Estimate the Hurst function by linear regression.
Hest1, c1 = Estimate_HurstFunction(scales, v)

#: 2.2. Estimate the Hurst function by minimisation without regularization.
scales2 = power(scales, 2)
logscales = log(scales2)
w = v.reshape((v.size,), order="F")

Hest2 = ones(Hest1.shape)
Hest2[:] = Hest1[:]
Hest2 = minimum(maximum(0.0001, Hest2), 0.9999)
pb = Minimize(Hest2, w, Ffun, DFfun, scales2, logscales, 0)
param = Parameters()
param.load("plot_mfbm.ini")
param.reg.name = None
pb.params = param
print("Optimization without regularization:")
Hest2, c2 = pb.argmin_h()
h2value = pb.h_value()


#: 2.3. Estimate of the Hurst function by minimisation with a regularization.
#: Regularization parameter x.
Hest3 = ones(Hest2.shape)
Hest3[:] = Hest1[:]
Hest3 = minimum(maximum(0.0001, Hest3), 0.9999)
pb = Minimize(Hest3, w, Ffun, DFfun, scales2, logscales, 0)
param.load("./plot_mfbm.ini")
param.reg.name = 'tv-1d'
pb.params = param
print("Optimization with TV regularization:")
Hest3, c3 = pb.argmin_h()

#: 3. Plot results.
plt.figure(1)
w_size2 = w_size // 2
plt.plot(y)
plt.title("Multifractional Brownian field")
plt.xlabel('Time')
plt.show()

plt.figure(2)
w_size2 = w_size // 2
t = arange(H.size)
t = t[w_size2:-w_size2:w_step]
t = t[0:Hest1.size]
Htrue = H[w_size2:-w_size2:w_step]
Htrue = Htrue[0:Hest1.size]
plt.plot(t, Htrue, label="Ground truth")
plt.plot(t, Hest1, label="Estimation by linear regression")
plt.plot(t, Hest2, label="Optimization without regularization")
plt.plot(t, Hest3, label="Optimization with TV regulariation")
plt.title("Estimation of the Hurst function.")
plt.legend()
plt.xlabel('Time')
plt.ylabel('Hurst value')
plt.show()
Optimization without regularization:
varprox reg = None   | iter    0 / 1000: cost = 4.871723e+01 improved by 2.7187 percent.
varprox reg = None   | iter    1 / 1000: cost = 4.772748e+01 improved by 2.0316 percent.
varprox reg = None   | iter    2 / 1000: cost = 4.699879e+01 improved by 1.5268 percent.
varprox reg = None   | iter    3 / 1000: cost = 4.645678e+01 improved by 1.1532 percent.
varprox reg = None   | iter    4 / 1000: cost = 4.605011e+01 improved by 0.8754 percent.
varprox reg = None   | iter    5 / 1000: cost = 4.574272e+01 improved by 0.6675 percent.
varprox reg = None   | iter    6 / 1000: cost = 4.550898e+01 improved by 0.5110 percent.
varprox reg = None   | iter    7 / 1000: cost = 4.533013e+01 improved by 0.3930 percent.
varprox reg = None   | iter    8 / 1000: cost = 4.519261e+01 improved by 0.3034 percent.
varprox reg = None   | iter    9 / 1000: cost = 4.508640e+01 improved by 0.2350 percent.
varprox reg = None   | iter   10 / 1000: cost = 4.500405e+01 improved by 0.1827 percent.
varprox reg = None   | iter   11 / 1000: cost = 4.494002e+01 improved by 0.1423 percent.
varprox reg = None   | iter   12 / 1000: cost = 4.489002e+01 improved by 0.1113 percent.
varprox reg = None   | iter   13 / 1000: cost = 4.485085e+01 improved by 0.0872 percent.
varprox reg = None   | iter   14 / 1000: cost = 4.482009e+01 improved by 0.0686 percent.
varprox reg = None   | iter   15 / 1000: cost = 4.479586e+01 improved by 0.0541 percent.
varprox reg = None   | iter   16 / 1000: cost = 4.477674e+01 improved by 0.0427 percent.
varprox reg = None   | iter   17 / 1000: cost = 4.476164e+01 improved by 0.0337 percent.
varprox reg = None   | iter   18 / 1000: cost = 4.474966e+01 improved by 0.0268 percent.
varprox reg = None   | iter   19 / 1000: cost = 4.474013e+01 improved by 0.0213 percent.
varprox reg = None   | iter   20 / 1000: cost = 4.473255e+01 improved by 0.0170 percent.
varprox reg = None   | iter   21 / 1000: cost = 4.472650e+01 improved by 0.0135 percent.
varprox reg = None   | iter   22 / 1000: cost = 4.472166e+01 improved by 0.0108 percent.
varprox reg = None   | iter   23 / 1000: cost = 4.471780e+01 improved by 0.0086 percent.
varprox reg = None   | iter   24 / 1000: cost = 4.471471e+01 improved by 0.0069 percent.
varprox reg = None   | iter   25 / 1000: cost = 4.471222e+01 improved by 0.0056 percent.
varprox reg = None   | iter   26 / 1000: cost = 4.471021e+01 improved by 0.0045 percent.
varprox reg = None   | iter   27 / 1000: cost = 4.470859e+01 improved by 0.0036 percent.
varprox reg = None   | iter   28 / 1000: cost = 4.470729e+01 improved by 0.0029 percent.
varprox reg = None   | iter   29 / 1000: cost = 4.470624e+01 improved by 0.0023 percent.
varprox reg = None   | iter   30 / 1000: cost = 4.470539e+01 improved by 0.0019 percent.
varprox reg = None   | iter   31 / 1000: cost = 4.470470e+01 improved by 0.0015 percent.
varprox reg = None   | iter   32 / 1000: cost = 4.470414e+01 improved by 0.0013 percent.
varprox reg = None   | iter   33 / 1000: cost = 4.470368e+01 improved by 0.0010 percent.
varprox reg = None   | iter   34 / 1000: cost = 4.470331e+01 improved by 0.0008 percent.
Optimization with TV regularization:
varprox reg = tv-1d  | iter    0 / 1000: cost = 4.991315e+01 improved by 0.3308 percent.
varprox reg = tv-1d  | iter    1 / 1000: cost = 4.976913e+01 improved by 0.2885 percent.
varprox reg = tv-1d  | iter    2 / 1000: cost = 4.962954e+01 improved by 0.2805 percent.
varprox reg = tv-1d  | iter    3 / 1000: cost = 4.949527e+01 improved by 0.2705 percent.
varprox reg = tv-1d  | iter    4 / 1000: cost = 4.936347e+01 improved by 0.2663 percent.
varprox reg = tv-1d  | iter    5 / 1000: cost = 4.922758e+01 improved by 0.2753 percent.
varprox reg = tv-1d  | iter    6 / 1000: cost = 4.911156e+01 improved by 0.2357 percent.
varprox reg = tv-1d  | iter    7 / 1000: cost = 4.898505e+01 improved by 0.2576 percent.
varprox reg = tv-1d  | iter    8 / 1000: cost = 4.887600e+01 improved by 0.2226 percent.
varprox reg = tv-1d  | iter    9 / 1000: cost = 4.876143e+01 improved by 0.2344 percent.
varprox reg = tv-1d  | iter   10 / 1000: cost = 4.866468e+01 improved by 0.1984 percent.
varprox reg = tv-1d  | iter   11 / 1000: cost = 4.855736e+01 improved by 0.2205 percent.
varprox reg = tv-1d  | iter   12 / 1000: cost = 4.847028e+01 improved by 0.1793 percent.
varprox reg = tv-1d  | iter   13 / 1000: cost = 4.836540e+01 improved by 0.2164 percent.
varprox reg = tv-1d  | iter   14 / 1000: cost = 4.826655e+01 improved by 0.2044 percent.
varprox reg = tv-1d  | iter   15 / 1000: cost = 4.816105e+01 improved by 0.2186 percent.
varprox reg = tv-1d  | iter   16 / 1000: cost = 4.809999e+01 improved by 0.1268 percent.
varprox reg = tv-1d  | iter   17 / 1000: cost = 4.801953e+01 improved by 0.1673 percent.
varprox reg = tv-1d  | iter   18 / 1000: cost = 4.793994e+01 improved by 0.1657 percent.
varprox reg = tv-1d  | iter   19 / 1000: cost = 4.784168e+01 improved by 0.2050 percent.
varprox reg = tv-1d  | iter   20 / 1000: cost = 4.777911e+01 improved by 0.1308 percent.
varprox reg = tv-1d  | iter   21 / 1000: cost = 4.766569e+01 improved by 0.2374 percent.
varprox reg = tv-1d  | iter   22 / 1000: cost = 4.759944e+01 improved by 0.1390 percent.
varprox reg = tv-1d  | iter   23 / 1000: cost = 4.753237e+01 improved by 0.1409 percent.
varprox reg = tv-1d  | iter   24 / 1000: cost = 4.748595e+01 improved by 0.0977 percent.
varprox reg = tv-1d  | iter   25 / 1000: cost = 4.740143e+01 improved by 0.1780 percent.
varprox reg = tv-1d  | iter   26 / 1000: cost = 4.734726e+01 improved by 0.1143 percent.
varprox reg = tv-1d  | iter   27 / 1000: cost = 4.727829e+01 improved by 0.1457 percent.
varprox reg = tv-1d  | iter   28 / 1000: cost = 4.723986e+01 improved by 0.0813 percent.
varprox reg = tv-1d  | iter   29 / 1000: cost = 4.716332e+01 improved by 0.1620 percent.
varprox reg = tv-1d  | iter   30 / 1000: cost = 4.713903e+01 improved by 0.0515 percent.
varprox reg = tv-1d  | iter   31 / 1000: cost = 4.706439e+01 improved by 0.1583 percent.
varprox reg = tv-1d  | iter   32 / 1000: cost = 4.704472e+01 improved by 0.0418 percent.
varprox reg = tv-1d  | iter   33 / 1000: cost = 4.698764e+01 improved by 0.1213 percent.
varprox reg = tv-1d  | iter   34 / 1000: cost = 4.696535e+01 improved by 0.0475 percent.
varprox reg = tv-1d  | iter   35 / 1000: cost = 4.690497e+01 improved by 0.1286 percent.
varprox reg = tv-1d  | iter   36 / 1000: cost = 4.688323e+01 improved by 0.0464 percent.
varprox reg = tv-1d  | iter   37 / 1000: cost = 4.682014e+01 improved by 0.1346 percent.
varprox reg = tv-1d  | iter   38 / 1000: cost = 4.680078e+01 improved by 0.0413 percent.
varprox reg = tv-1d  | iter   39 / 1000: cost = 4.676464e+01 improved by 0.0772 percent.
varprox reg = tv-1d  | iter   40 / 1000: cost = 4.673086e+01 improved by 0.0722 percent.
varprox reg = tv-1d  | iter   41 / 1000: cost = 4.670082e+01 improved by 0.0643 percent.
varprox reg = tv-1d  | iter   42 / 1000: cost = 4.664505e+01 improved by 0.1194 percent.
varprox reg = tv-1d  | iter   43 / 1000: cost = 4.660998e+01 improved by 0.0752 percent.
varprox reg = tv-1d  | iter   44 / 1000: cost = 4.660805e+01 improved by 0.0041 percent.
varprox reg = tv-1d  | iter   45 / 1000: cost = 4.657633e+01 improved by 0.0681 percent.
varprox reg = tv-1d  | iter   46 / 1000: cost = 4.656256e+01 improved by 0.0296 percent.
varprox reg = tv-1d  | iter   47 / 1000: cost = 4.655503e+01 improved by 0.0162 percent.
varprox reg = tv-1d  | iter   48 / 1000: cost = 4.652088e+01 improved by 0.0734 percent.
varprox reg = tv-1d  | iter   49 / 1000: cost = 4.651528e+01 improved by 0.0120 percent.
varprox reg = tv-1d  | iter   50 / 1000: cost = 4.648020e+01 improved by 0.0754 percent.
varprox reg = tv-1d  | iter   51 / 1000: cost = 4.646190e+01 improved by 0.0394 percent.
varprox reg = tv-1d  | iter   52 / 1000: cost = 4.643693e+01 improved by 0.0538 percent.
varprox reg = tv-1d  | iter   53 / 1000: cost = 4.643048e+01 improved by 0.0139 percent.
varprox reg = tv-1d  | iter   54 / 1000: cost = 4.639463e+01 improved by 0.0772 percent.
varprox reg = tv-1d  | iter   55 / 1000: cost = 4.637213e+01 improved by 0.0485 percent.
varprox reg = tv-1d  | iter   56 / 1000: cost = 4.634708e+01 improved by 0.0540 percent.
varprox reg = tv-1d  | iter   57 / 1000: cost = 4.636097e+01 improved by -0.0300 percent.
varprox reg = tv-1d  | iter   58 / 1000: cost = 4.634460e+01 improved by 0.0053 percent.
varprox reg = tv-1d  | iter   59 / 1000: cost = 4.632226e+01 improved by 0.0482 percent.
varprox reg = tv-1d  | iter   60 / 1000: cost = 4.631125e+01 improved by 0.0238 percent.
varprox reg = tv-1d  | iter   61 / 1000: cost = 4.629265e+01 improved by 0.0402 percent.
varprox reg = tv-1d  | iter   62 / 1000: cost = 4.628327e+01 improved by 0.0203 percent.
varprox reg = tv-1d  | iter   63 / 1000: cost = 4.624632e+01 improved by 0.0798 percent.
varprox reg = tv-1d  | iter   64 / 1000: cost = 4.626386e+01 improved by -0.0379 percent.
varprox reg = tv-1d  | iter   65 / 1000: cost = 4.624016e+01 improved by 0.0133 percent.
varprox reg = tv-1d  | iter   66 / 1000: cost = 4.625058e+01 improved by -0.0225 percent.
varprox reg = tv-1d  | iter   67 / 1000: cost = 4.624805e+01 improved by -0.0171 percent.
varprox reg = tv-1d  | iter   68 / 1000: cost = 4.623286e+01 improved by 0.0158 percent.
varprox reg = tv-1d  | iter   69 / 1000: cost = 4.622920e+01 improved by 0.0079 percent.
varprox reg = tv-1d  | iter   70 / 1000: cost = 4.619371e+01 improved by 0.0768 percent.
varprox reg = tv-1d  | iter   71 / 1000: cost = 4.619124e+01 improved by 0.0053 percent.
varprox reg = tv-1d  | iter   72 / 1000: cost = 4.617511e+01 improved by 0.0349 percent.
varprox reg = tv-1d  | iter   73 / 1000: cost = 4.618159e+01 improved by -0.0140 percent.
varprox reg = tv-1d  | iter   74 / 1000: cost = 4.616304e+01 improved by 0.0261 percent.
varprox reg = tv-1d  | iter   75 / 1000: cost = 4.615118e+01 improved by 0.0257 percent.
varprox reg = tv-1d  | iter   76 / 1000: cost = 4.615344e+01 improved by -0.0049 percent.
varprox reg = tv-1d  | iter   77 / 1000: cost = 4.613789e+01 improved by 0.0288 percent.
varprox reg = tv-1d  | iter   78 / 1000: cost = 4.612447e+01 improved by 0.0291 percent.
varprox reg = tv-1d  | iter   79 / 1000: cost = 4.614116e+01 improved by -0.0362 percent.
varprox reg = tv-1d  | iter   80 / 1000: cost = 4.614130e+01 improved by -0.0365 percent.
varprox reg = tv-1d  | iter   81 / 1000: cost = 4.613393e+01 improved by -0.0205 percent.
varprox reg = tv-1d  | iter   82 / 1000: cost = 4.611576e+01 improved by 0.0189 percent.
varprox reg = tv-1d  | iter   83 / 1000: cost = 4.613788e+01 improved by -0.0480 percent.
varprox reg = tv-1d  | iter   84 / 1000: cost = 4.610381e+01 improved by 0.0259 percent.
varprox reg = tv-1d  | iter   85 / 1000: cost = 4.608010e+01 improved by 0.0514 percent.
varprox reg = tv-1d  | iter   86 / 1000: cost = 4.609143e+01 improved by -0.0246 percent.
varprox reg = tv-1d  | iter   87 / 1000: cost = 4.608519e+01 improved by -0.0110 percent.
varprox reg = tv-1d  | iter   88 / 1000: cost = 4.609716e+01 improved by -0.0370 percent.
varprox reg = tv-1d  | iter   89 / 1000: cost = 4.611744e+01 improved by -0.0810 percent.
varprox reg = tv-1d  | iter   90 / 1000: cost = 4.612909e+01 improved by -0.1062 percent.
varprox reg = tv-1d  | iter   91 / 1000: cost = 4.611476e+01 improved by -0.0751 percent.
varprox reg = tv-1d  | iter   92 / 1000: cost = 4.609899e+01 improved by -0.0410 percent.
varprox reg = tv-1d  | iter   93 / 1000: cost = 4.608856e+01 improved by -0.0184 percent.
varprox reg = tv-1d  | iter   94 / 1000: cost = 4.608134e+01 improved by -0.0027 percent.
varprox reg = tv-1d  | iter   95 / 1000: cost = 4.607226e+01 improved by 0.0170 percent.
varprox reg = tv-1d  | iter   96 / 1000: cost = 4.606127e+01 improved by 0.0239 percent.
varprox reg = tv-1d  | iter   97 / 1000: cost = 4.607712e+01 improved by -0.0344 percent.
varprox reg = tv-1d  | iter   98 / 1000: cost = 4.608495e+01 improved by -0.0514 percent.
varprox reg = tv-1d  | iter   99 / 1000: cost = 4.610309e+01 improved by -0.0907 percent.
varprox reg = tv-1d  | iter  100 / 1000: cost = 4.608040e+01 improved by -0.0415 percent.
varprox reg = tv-1d  | iter  101 / 1000: cost = 4.609075e+01 improved by -0.0640 percent.
varprox reg = tv-1d  | iter  102 / 1000: cost = 4.609347e+01 improved by -0.0698 percent.
varprox reg = tv-1d  | iter  103 / 1000: cost = 4.613002e+01 improved by -0.1491 percent.
varprox reg = tv-1d  | iter  104 / 1000: cost = 4.606147e+01 improved by -0.0004 percent.
../_images/auto_examples_plot_mfbm_1_1.png
../_images/auto_examples_plot_mfbm_1_2.png