Skip to content

example005_comparison_pyabel

This example provides a rather extensive comparison of different openAbel methods with PyAbel methods. It shows how well the main methods of openAbel perform in every regard, especially if the input data is sufficiently smooth.

Since PyAbel's focus is on the backward (or inverse) transform, this example does it as well.

Comparison of different openAbel with PyAbel methods.

############################################################################################################################################
# Example to showcase the different openAbel methods in comparison with PyAbel methods.
# Backward transform is done here since PyAbel focuses on that.
# One obviously needs PyAbel installed for this example
############################################################################################################################################


import glob
import os
import time as ti

import abel
import matplotlib.pyplot as mpl
import numpy as np

import openabel as oa

############################################################################################################################################
# Plotting setup

params = {
    "axes.labelsize": 8,
    "font.size": 8,
    "legend.fontsize": 8,
    "xtick.labelsize": 10,
    "ytick.labelsize": 10,
    "text.usetex": False,
    "figure.figsize": [16.0, 8.0],
}
mpl.rcParams.update(params)
# Color scheme
colors = [
    "#005AA9",
    "#E6001A",
    "#99C000",
    "#721085",
    "#EC6500",
    "#009D81",
    "#A60084",
    "#0083CC",
    "#F5A300",
    "#C9D400",
    "#FDCA00",
]
# Plot markers
markers = ["o", "v", "s", "D", "p", "*", "h", "+", "^", "x"]
# Line styles
linestyles = ["-", "--", "-.", ":", "-", "--", "-.", ":", "-", "--", "-.", ":"]
lw = 2

fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = mpl.subplots(2, 3, layout="constrained")


############################################################################################################################################
# Error over radius of different methods and orders


def error_abel(n_data, method, order):

    dx = 1.0 / (n_data - 1)
    xx = np.linspace(0.0, 1.0, n_data)

    data_in = 3.0 / 8.0 * np.pi * (1 - xx**2) ** 2
    data_ana = np.sqrt(1 - xx**2) ** 3
    abel_obj = oa.Abel(n_data, 1, 0.0, dx, method=method, order=order)
    data_out = abel_obj.execute(data_in)
    abserr = data_out - data_ana
    relerr = np.abs(abserr / np.clip(data_ana, 1.0e-300, None))

    return (xx, abserr, relerr, data_out, data_ana)


# Loop over several methods and orders
names = ["HL", "FMM 2nd", "FMM 5th"]
orders = [-1, 2, 5]
methods = [1, 3, 3]

for ii in range(len(orders)):
    (xx, abserr, relerr, data_out, data_ana) = error_abel(30, methods[ii], orders[ii])
    ax1.plot(
        xx,
        data_out,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )
    ax2.plot(
        xx,
        abserr,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )
    ax3.semilogy(
        xx[:-1],
        relerr[:-1],
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )

ii += 1
ax1.plot(
    xx,
    data_ana,
    label="analytical",
    color=colors[ii],
    linestyle=linestyles[ii],
    marker=markers[ii],
    linewidth=lw,
)


def error_pyabel(n_data, method, function):

    dx = 1.0 / (n_data - 1)
    xx = np.linspace(0.0, 1.0, n_data)

    data_in = 3.0 / 8.0 * np.pi * (1 - xx**2) ** 2
    data_ana = np.sqrt(1 - xx**2) ** 3
    data_out = function(data_in, basis_dir=".", dr=dx, direction="inverse")
    abserr = data_out - data_ana
    relerr = np.abs(abserr / np.clip(data_ana, 1.0e-300, None))

    return (xx, abserr, relerr, data_out, data_ana)


jj = ii + 1
# Loop over several methods and orders
methods = [
    "three_point",
    "two_point",
    "onion_peeling",
    "hansenlaw",
    "basex",
    "direct",
]
functions = [
    abel.dasch.three_point_transform,
    abel.dasch.two_point_transform,
    abel.dasch.onion_peeling_transform,
    abel.hansenlaw.hansenlaw_transform,
    abel.basex.basex_transform,
    abel.direct.direct_transform,
]
for ii in range(len(methods)):
    (xx, abserr, relerr, data_out, data_ana) = error_pyabel(30, methods[ii], functions[ii])
    ax1.plot(
        xx,
        data_out,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )
    ax2.plot(
        xx,
        abserr,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )
    ax3.semilogy(
        xx[:-1],
        relerr[:-1],
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )


ax1.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0))
ax1.set_xlabel("radius")
ax1.set_ylabel("value")
ax1.grid(True)

ax2.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0))
ax2.set_xlabel("radius")
ax2.set_ylabel("absolute error")
ax2.grid(True)

ax3.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0))
ax3.set_xlabel("radius")
ax3.set_ylabel("relative error")
ax3.grid(True)

#############################################################################################################################################
# Convergence of different methods


def convergence_abel(n_array, method, order):

    conv = np.empty(n_array.shape[0])
    for ii in range(n_array.shape[0]):
        n_data = n_array[ii]
        dx = 1.0 / (n_data - 1)
        xx = np.linspace(0.0, 1.0, n_data)

        data_in = 3.0 / 8.0 * np.pi * (1 - xx**2) ** 2
        abel_obj = oa.Abel(n_data, 1, 0.0, dx, method=method, order=order)
        data_out = abel_obj.execute(data_in)
        data_ana = np.sqrt(1 - xx**2) ** 3

        conv[ii] = np.sqrt(np.sum(((data_out[:-1] - data_ana[:-1]) / data_ana[:-1]) ** 2) / (n_data - 1))

    return conv


# Loop over several methods and orders
names = ["HL", "FMM 1st", "FMM 5th"]
orders = [-1, 1, 5]
methods = [1, 3, 3]
n_array = 10 ** (np.arange(5) + 2)

for ii in range(len(orders)):
    conv = convergence_abel(n_array, methods[ii], orders[ii])
    ax4.loglog(
        n_array,
        conv,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )


def convergence_pyabel(n_array, method, function):

    conv = np.empty(n_array.shape[0])
    for ii in range(n_array.shape[0]):
        n_data = n_array[ii]
        dx = 1.0 / (n_data - 1)
        xx = np.linspace(0.0, 1.0, n_data)

        data_in = 3.0 / 8.0 * np.pi * (1 - xx**2) ** 2
        data_out = function(data_in, basis_dir=".", dr=dx, direction="inverse")
        data_ana = np.sqrt(1 - xx**2) ** 3

        conv[ii] = np.sqrt(np.sum(((data_out[:-1] - data_ana[:-1]) / data_ana[:-1]) ** 2) / (n_data - 1))

    return conv


jj = ii + 1
# Loop over several methods
methods = ["hansenlaw", "onion_peeling", "three_point", "two_point", "basex", "direct"]
functions = [
    abel.hansenlaw.hansenlaw_transform,
    abel.dasch.onion_peeling_transform,
    abel.dasch.three_point_transform,
    abel.dasch.two_point_transform,
    abel.basex.basex_transform,
    abel.direct.direct_transform,
]
for ii in range(1):
    conv = convergence_pyabel(n_array, methods[ii], functions[ii])
    ax4.loglog(
        n_array,
        conv,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )

n_array = (10 ** (np.arange(4) * 0.5 + 2) + 0.5).astype(int)
for ii in range(1, len(methods)):
    conv = convergence_pyabel(n_array, methods[ii], functions[ii])
    ax4.loglog(
        n_array,
        conv,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )


ax4.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0))
ax4.set_xlabel("number of data points")
ax4.set_ylabel("relative error")
ax4.grid(True)


#############################################################################################################################################
# Run times of different methods and orders


def runtimes_abel(n_array, n_measure, method, order):

    runtimes = np.zeros(n_array.shape[0])
    runtimes_pre = np.zeros(n_array.shape[0])

    for ii in range(n_array.shape[0]):
        data_in = np.ones(n_array[ii])
        timings = np.empty(n_measure)
        for jj in range(n_measure):
            t0 = ti.time()
            abel_obj = oa.Abel(n_array[ii], 1, 0.0, 1.0, method=method, order=order)
            t1 = ti.time()
            timings[jj] = t1 - t0
        runtimes_pre[ii] = np.sum(timings) / n_measure

        abel_obj = oa.Abel(n_array[ii], 1, 0.0, 1.0, method=method, order=order)
        t0 = ti.time()
        for jj in range(n_measure):
            data_out = abel_obj.execute(data_in)
        t1 = ti.time()

        runtimes[ii] = (t1 - t0) / n_measure

    return (runtimes_pre, runtimes)


# Loop over several methods and orders
names = ["HL", "FMM 3rd", "FMM 11th"]
orders = [-1, 3, 11]
methods = [1, 3, 3]
n_array = 10 ** (np.arange(5) + 2)

for ii in range(3):
    (runtimes_pre, runtimes) = runtimes_abel(n_array, 1, methods[ii], orders[ii])
    ax5.loglog(
        n_array,
        runtimes_pre,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )
    ax6.loglog(
        n_array,
        runtimes,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )

n_array = 10 ** (np.arange(4) + 2)
for ii in range(3, len(names)):
    (runtimes_pre, runtimes) = runtimes_abel(n_array, 1, methods[ii], orders[ii])
    ax5.loglog(
        n_array,
        runtimes_pre,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )
    ax6.loglog(
        n_array,
        runtimes,
        label=str(names[ii]),
        color=colors[ii],
        linestyle=linestyles[ii],
        marker=markers[ii],
        linewidth=lw,
    )


def runtimes_pyabel(n_array, n_measure, method, function):

    runtimes = np.zeros(n_array.shape[0])
    runtimes_pre = np.zeros(n_array.shape[0])

    for ii in range(n_array.shape[0]):
        data_in = np.ones(n_array[ii])
        timings = np.empty(n_measure)
        for jj in range(n_measure):
            for filename in glob.glob(method + "*"):
                os.remove(filename)
            t0 = ti.time()
            data_out = function(data_in, basis_dir=".", dr=1.0, direction="inverse")
            t1 = ti.time()
            timings[jj] = t1 - t0
        runtimes_pre[ii] = np.sum(timings) / n_measure

        t0 = ti.time()
        for jj in range(n_measure):
            data_out = function(data_in, basis_dir=".", dr=1.0, direction="inverse")
        t1 = ti.time()

        runtimes[ii] = (t1 - t0) / n_measure
        runtimes_pre[ii] -= runtimes[ii]

    return (runtimes_pre, runtimes)


jj = ii + 1
# Loop over several methods
methods = ["hansenlaw", "onion_peeling", "three_point", "two_point", "basex", "direct"]
functions = [
    abel.hansenlaw.hansenlaw_transform,
    abel.dasch.onion_peeling_transform,
    abel.dasch.three_point_transform,
    abel.dasch.two_point_transform,
    abel.basex.basex_transform,
    abel.direct.direct_transform,
]
n_array = 10 ** (np.arange(5) + 2)
for ii in range(1):
    (runtimes_pre, runtimes) = runtimes_pyabel(n_array, 1, methods[ii], functions[ii])
    ax6.loglog(
        n_array,
        runtimes,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )

n_array = (10 ** (np.arange(4) * 0.5 + 2) + 0.5).astype(int)
for ii in range(1, len(methods)):
    (runtimes_pre, runtimes) = runtimes_pyabel(n_array, 1, methods[ii], functions[ii])
    ax5.loglog(
        n_array,
        runtimes_pre,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )
    ax6.loglog(
        n_array,
        runtimes,
        label="PA: " + str(methods[ii]),
        color=colors[jj + ii],
        linestyle=linestyles[jj + ii],
        marker=markers[jj + ii],
        linewidth=lw,
    )

ax5.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0))
ax5.set_xlabel("number of data points")
ax5.set_ylabel("run time pre computation in s")
ax5.grid(True)

ax6.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0))
ax6.set_xlabel("number of data points")
ax6.set_ylabel("run time main computation in s")
ax6.grid(True)


mpl.savefig("example005_comparison_pyabel.png", dpi=300)

mpl.show()