Computing moments of the PSD¶

In [1]:
import numpy as np
import pytest

from SurfaceTopography import read_container, SurfaceContainer
from SurfaceTopography.Generation import fourier_synthesis
from SurfaceTopography.Generic.Moments import (compute_iso_moment,
                               compute_1d_moment, )
In [2]:
import SurfaceTopography
SurfaceTopography.__version__
Out[2]:
'1.3.0.dev70+gf7fb45bf.dirty'
In [3]:
import matplotlib.pyplot as plt

C1D¶

In [4]:
fig, axes = plt.subplots(3,1, sharex = True)

for seed in range(10):

    np.random.seed(seed)
    sx = 2
    nx = 1024

    unit = "m"
    t = fourier_synthesis((nx,), physical_sizes=(sx,), hurst=0.8, rms_height=1,
                          short_cutoff=4 * (sx / nx),
                          long_cutoff=sx / 8, unit=unit).detrend(detrend_mode="center")


    # Moments from full integration of the 2D spectrum of the topopography
    t_varh_ciso = t.moment_power_spectrum(order=0, )
    t_varhp_ciso = t.moment_power_spectrum(order=2, )
    t_varhpp_ciso = t.moment_power_spectrum(order=4, )
    for nb_points_per_decade in [5, 10, 20]:
        # Moment of the isotropic PSD computed from the 1D power spectrum
        # this means that there is resampling 
        c = SurfaceContainer([t, ])
        c_varh_ciso = c.c1d_moment(order=0, unit=unit, nb_points_per_decade=nb_points_per_decade)
        c_varhp_ciso = c.c1d_moment(order=2, unit=unit, nb_points_per_decade=nb_points_per_decade)
        c_varhpp_ciso = c.c1d_moment(order=4, unit=unit, nb_points_per_decade=nb_points_per_decade)


        style=dict(color="k", ls="none",marker="+")
        # assert abs(1 - c_varhp_ciso / t_varhp_ciso) < 0.05
        # assert abs(1 - c_varhpp_ciso / t_varhpp_ciso) < 0.05
        axes[0].plot(nb_points_per_decade, abs(1 - c_varh_ciso / t_varh_ciso), **style)
        axes[1].plot(nb_points_per_decade, abs(1 - c_varhp_ciso / t_varhp_ciso), **style)    
        axes[2].plot(nb_points_per_decade, abs(1 - c_varhpp_ciso / t_varhpp_ciso), **style)  

axes[0].set_ylabel(fr"$m_0$")
axes[1].set_ylabel(fr"$m_1$")
axes[2].set_ylabel(fr"$m_2$")

for ax in axes:
    ax.set_xscale("log")
    ax.set_yscale("log")
    ax.set_xlabel("nb points per decade")
    ax.label_outer()
/Users/antoines/Repositories/SurfaceTopography/SurfaceTopography/Container/Averaging.py:106: RuntimeWarning: Mean of empty slice
  avgresults = np.array(sorted([np.nanmean(vals, axis=0) for vals in results.values()], key=lambda x: x[0]))
In [5]:
c1d_moment=c._functions["c1d_moment"]
In [6]:
??c1d_moment
In [7]:
??compute_1d_moment

Ciso¶

In [8]:
fig, axes = plt.subplots(3,1, sharex = True)

for seed in range(10):

    np.random.seed(seed)
    sx, sy = physical_sizes = 2, 3
    nx, ny = nb_grid_pts = 1024, 1023

    unit = "m"
    t = fourier_synthesis(nb_grid_pts, physical_sizes=physical_sizes, hurst=0.8, rms_height=1,
                          short_cutoff=4 * (sx / nx),
                          long_cutoff=sx / 8, unit=unit).detrend(detrend_mode="center")

    # Moments from full integration of the 2D spectrum of the topopography
    t_varh_ciso = t.moment_power_spectrum(order=0, )
    t_varhp_ciso = t.moment_power_spectrum(order=2, )
    t_varhpp_ciso = t.moment_power_spectrum(order=4, )
    for nb_points_per_decade in [5, 10, 20]:
        # Moment of the isotropic PSD computed from the 1D power spectrum
        # this means that there is resampling 
        c = SurfaceContainer([t, ])
        c_varh_ciso = c.ciso_moment(order=0, unit=unit, nb_points_per_decade=nb_points_per_decade)
        c_varhp_ciso = c.ciso_moment(order=2, unit=unit, nb_points_per_decade=nb_points_per_decade)
        c_varhpp_ciso = c.ciso_moment(order=4, unit=unit, nb_points_per_decade=nb_points_per_decade)


        style=dict(color="k", ls="none",marker="+")
        # assert abs(1 - c_varhp_ciso / t_varhp_ciso) < 0.05
        # assert abs(1 - c_varhpp_ciso / t_varhpp_ciso) < 0.05
        axes[0].plot(nb_points_per_decade, abs(1 - c_varh_ciso / t_varh_ciso), **style)
        axes[1].plot(nb_points_per_decade, abs(1 - c_varhp_ciso / t_varhp_ciso), **style)    
        axes[2].plot(nb_points_per_decade, abs(1 - c_varhpp_ciso / t_varhpp_ciso), **style)  
    
axes[0].set_ylabel(fr"$m_0$")
axes[1].set_ylabel(fr"$m_1$")
axes[2].set_ylabel(fr"$m_2$")    

for ax in axes:
    ax.set_xscale("log")
    ax.set_yscale("log")
    ax.set_xlabel("nb points per decade")
    ax.label_outer()
In [9]:
ciso_moment=c._functions["ciso_moment"]
In [10]:
ciso_moment??
In [11]:
??compute_iso_moment

Conclusions:¶

Computing the average PSD and than doing a trapezoidal integration leads to large errors in the PSD

Better approach:

  • average the integrand of the moment $q^\alpha C(q)$ and then integrate.
  • smarter binning
In [ ]: