Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vectorized overloads for array arguments #58

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

brandonwillard
Copy link
Contributor

@brandonwillard brandonwillard commented Apr 20, 2021

This PR dynamically creates Python functions in order to vectorize the functions exposed by numba-scipy. This can serve as a stand-in until numba/numba#6954 is addressed.

Closes #56.

@esc esc added the 3 - Ready for Review PR is ready for review label Apr 21, 2021
@PabloRdrRbl
Copy link

Hello, will this get merged?

@esc
Copy link
Member

esc commented Apr 9, 2024

Hello, will this get merged?

hopefully at some point..

@Gabri110
Copy link

Gabri110 commented Jan 15, 2025

Hi,

I did some simple testing of this PR and I saw that (for my particular case) I didn't gain any performance. In it, I compared the performance of running both quad and quad_vec with a non-JITed and with a JITed integrand. My results are the following:

Execution time for perform_integrals_quad: 0.40845513343811035 seconds
Execution time for perform_integrals_quad_numba: 0.06902384757995605 seconds
Execution time for perform_integrals_quad_vec: 0.20749497413635254 seconds
Execution time for perform_integrals_quad_vec_numba: 0.23479509353637695 seconds

So I actually lost performance when using both quad_vec and numba, compared to only using one of them.

Here's the code, in case that you want to test it yourselves:

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import j0, j1
from scipy.integrate import quad, quad_vec

# pip install git+https://github.com/brandonwillard/numba-scipy.git@vectorize-overloads #egg=numba-scipy
from numba import jit, njit

import time



# We store the range of n, kn and gn in arrays of length N_max
N_max = 20
n_values = np.linspace(0, N_max-1, N_max, dtype=int)
k_n_values = 2 * np.pi * n_values


def perform_integrals_quad(n, t, z, epsilon = 1e-1):
    '''
    We perform the integrals primarily using quad
    '''

    # We create the matrix of possible u(t,z) values, defined as u^2 = t^2 - z^2
    def u(t,z):
        return np.where(t <= z, 0, np.sqrt(np.maximum(t**2 - z**2, 0))) 
    
    u_value = u(t, z)

    k_n = 2*np.pi*n

    # We compute the integral from 0 to epsilon
    def integrand(u,k):
        return np.sin(k * u) / u * j1(k*u)

    epsilon_part,_ = quad(integrand, epsilon, u_value, args=(k_n), limit=100000, maxp1=100, epsabs=1e-6, epsrel=1e-3) # We use quad

    # We add everything and return it
    return epsilon_part




start_time = time.time()  # Record start time
# We compute the integral from 0 to epsilon
@njit
def integrand_numba(u,k):
    return np.sin(k * u) / u * j1(k*u)
integrand_numba(1.,2.)
end_time = time.time()    # Record end time
execution_time_one = end_time - start_time  # Calculate elapsed time
print(f"Execution time for compilation of integrand_numba: {execution_time_one} seconds")

def perform_integrals_quad_numba(integrand, n, t, z, epsilon = 1e-1):
    '''
    We perform the integrals using quad and first JITing the integrand 
    '''

    # We create the matrix of possible u(t,z) values, defined as u^2 = t^2 - z^2
    def u(t,z):
        return np.where(t <= z, 0, np.sqrt(np.maximum(t**2 - z**2, 0))) 
    
    u_value = u(t, z)

    k_n = 2*np.pi*n

    
    epsilon_part,_ = quad(integrand, epsilon, u_value, args=(k_n), limit=100000, maxp1=100, epsabs=1e-6, epsrel=1e-3) # We use quad

    return epsilon_part




start_time = time.time()  # Record start time
# We compute the integral from 0 to epsilon
@njit(parallel=True)
def integrand_vec_numba(u):
    return np.sin(k_n_values * u) / u * j1(k_n_values*u)
integrand_vec_numba(1.)
end_time = time.time()    # Record end time
execution_time_one = end_time - start_time  # Calculate elapsed time
print(f"Execution time for compilation of integrand_vec_numba: {execution_time_one} seconds")

def perform_integrals_quad_vec_numba(integrand_vec_numba, t, z, epsilon = 1e-1):
    '''
    We perform the integrals using quadvec and first JITing the integrand 
    '''

    # We create the matrix of possible u(t,z) values, defined as u^2 = t^2 - z^2
    def u(t,z):
        return np.where(t <= z, 0, np.sqrt(np.maximum(t**2 - z**2, 0))) 
    
    u_value = u(t, z)


    epsilon_part,_ = quad_vec(integrand_vec_numba, epsilon, u_value, limit=100000, epsabs=1e-6, epsrel=1e-3, workers=1) # We use quad

    return epsilon_part



def integrand_vec(u):
        return np.sin(k_n_values * u) / u * j1(k_n_values*u)

def perform_integrals_quad_vec(integrand_vec, t, z, epsilon = 1e-1):
    '''
    We perform the integrals using quad_vec
    '''

    # We create the matrix of possible u(t,z) values, defined as u^2 = t^2 - z^2
    def u(t,z):
        return np.where(t <= z, 0, np.sqrt(np.maximum(t**2 - z**2, 0))) 
    
    u_value = u(t, z)


    epsilon_part,_ = quad_vec(integrand_vec, epsilon, u_value, limit=100000, epsabs=1e-6, epsrel=1e-3, workers=1) # We use quad


    return epsilon_part



# Exact integral
def perform_integrals_exact(n, t, z):
    k = 2*np.pi*n
    return - np.cos(k*t) * j0(k*t) - np.sin(k*t) * j1(k*t)




if __name__ == "__main__":

    epsilon = 2
    t = 25
    z = 0.

    quad_result = np.empty(len(n_values))
    start_time = time.time()  # Record start time
    for n in n_values:
        quad_result[n] = perform_integrals_quad(n, t, z, epsilon)
    end_time = time.time()    # Record end time
    execution_time_one = end_time - start_time  # Calculate elapsed time
    print(f"Execution time for perform_integrals_quad: {execution_time_one} seconds")


    quad_numba_result = np.empty(len(n_values))
    start_time = time.time()  # Record start time
    for n in n_values:
        quad_numba_result[n] = perform_integrals_quad_numba(integrand_numba, n, t, z, epsilon)
    end_time = time.time()    # Record end time
    execution_time_one = end_time - start_time  # Calculate elapsed time
    print(f"Execution time for perform_integrals_quad_numba: {execution_time_one} seconds")


    quad_vec_result = np.empty(len(n_values))
    start_time = time.time()  # Record start time
    quad_vec_result = perform_integrals_quad_vec(integrand_vec, t, z, epsilon)
    end_time = time.time()    # Record end time
    execution_time_one = end_time - start_time  # Calculate elapsed time
    print(f"Execution time for perform_integrals_quad_vec: {execution_time_one} seconds")


    quad_vec_numba_result = np.empty(len(n_values))
    start_time = time.time()  # Record start time
    quad_vec_numba_result = perform_integrals_quad_vec_numba(integrand_vec_numba, t, z, epsilon)
    end_time = time.time()    # Record end time
    execution_time_one = end_time - start_time  # Calculate elapsed time
    print(f"Execution time for perform_integrals_quad_vec_numba: {execution_time_one} seconds")



    exact_result = np.empty(len(n_values))
    for n in n_values:
        exact_result[n] = perform_integrals_exact(n, t, z) - perform_integrals_exact(n, epsilon, z)



    # Create the plot
    plt.plot(n_values, quad_numba_result, label='Quad Numba', color='blue', marker='o')
    plt.plot(n_values, quad_result, label='Normal quad', color='red', marker='o') 

    plt.plot(n_values, quad_vec_numba_result, label='Quad Vec', color='black', marker='o')
    plt.plot(n_values, quad_vec_result, label='Quad Vec Numba', color='purple', marker='o') 

    plt.plot(n_values, exact_result, label='Exact', color='green') 

    # Add labels and title
    plt.xlabel('n')
    plt.ylabel('Integral')
    plt.title('Integral in function of n for two different integration methods')

    # Show legend
    plt.legend()

    # Display the plot
    plt.show()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 - Ready for Review PR is ready for review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement array-valued signatures
5 participants