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

Optional numba performance boost needed? #11

Open
amogorkon opened this issue Apr 20, 2021 · 3 comments
Open

Optional numba performance boost needed? #11

amogorkon opened this issue Apr 20, 2021 · 3 comments

Comments

@amogorkon
Copy link
Collaborator

No description provided.

@amogorkon
Copy link
Collaborator Author

amogorkon commented Feb 28, 2022

Well, preliminary timing tests show that fuzzylogic functions are up to 3 times faster than the membership functions in scikit-fuzzy (while having more functionality/safety!), but it's not easy to make numba happy.

from time import perf_counter_ns as time
from statistics import mean, stdev, median

import numpy as np

from numba import jit, njit

from random import randint, random
from fuzzylogic.functions import gauss

def timeit(func):
    res = []
    for _ in range(100000):
        before = time()
        func()
        res.append(time() - before)
    for f in (min, mean, median, stdev):
        print(f(res))
    return res

def gaussmf(x, mean, sigma):
    """
    Gaussian fuzzy membership function.
    Parameters
    ----------
    x : 1d array or iterable
        Independent variable.
    mean : float
        Gaussian parameter for center (mean) value.
    sigma : float
        Gaussian parameter for standard deviation.
    Returns
    -------
    y : 1d array
        Gaussian membership function for x.
    """
    return np.exp(-((x - mean)**2.) / (2 * sigma**2.))

gauss_vars = [randint(0,50) for _ in range(100)]

@jit
def gauss_numba(c:float, b:float, *, c_m=1):
    """Defined by ae^(-b(x-x0)^2), a gaussian distribution.
    
    Basically a triangular sigmoid function, it comes close to human perception.

    vars
    ----
    c_m (a)
        defines the maximum y-value of the graph
    b
        defines the steepness
    c (x0)
        defines the symmetry center/peak of the graph
    """
    assert 0 < c_m <= 1
    assert 0 < b, "b must be greater than 0"

    def f(x):
        try:
            o = (x - c)**2
        except OverflowError:
            return 0
        return c_m * exp(-b * o)
    return f

def test_gaussmf():
    return [gaussmf(x, 10, 5) for x in gauss_vars]

def test_gauss():
    g = gauss(10,5)
    return [g(x) for x in gauss_vars]

def test_gauss_numba():
    g = gauss_numba(10,5, 1)
    return [g(x) for x in gauss_vars]

for x in (test_gauss_numba,):
    print(x.__name__)
    timeit(x)

fails with

Traceback (most recent call last):
  File "F:\Dropbox (Privat)\code\justuse\tests\.tests\.test2.py", line 81, in <module>
    timeit(x)
  File "F:\Dropbox (Privat)\code\justuse\tests\.tests\.test2.py", line 16, in timeit
    func()
  File "F:\Dropbox (Privat)\code\justuse\tests\.tests\.test2.py", line 76, in test_gauss_numba
    g = gauss_numba(10,5, 1)
  File "G:\Python398\lib\site-packages\numba\core\dispatcher.py", line 468, in _compile_for_args
    error_rewrite(e, 'typing')
  File "G:\Python398\lib\site-packages\numba\core\dispatcher.py", line 409, in error_rewrite
    raise e.with_traceback(None)
numba.core.errors.TypingError: Failed in object mode pipeline (step: convert make_function into JIT functions)
�[1mCannot capture the non-constant value associated with variable 'b' in a function that will escape.
�[1m
File ".test2.py", line 60:�[0m
�[1m
�[1m    def f(x):
�[0m    �[1m^�[0m�[0m
�[0m

@amogorkon
Copy link
Collaborator Author

amogorkon commented Feb 28, 2022

Okay, turns out it is possible to njit the inner functions of those closures, preserving the nice behaviour of the outer functions, but numba can't work with try/except, so this is feasible:

def gauss_numba(c:float, b:float, *, c_m=1):
    """Defined by ae^(-b(x-x0)^2), a gaussian distribution.
    
    Basically a triangular sigmoid function, it comes close to human perception.

    vars
    ----
    c_m (a)
        defines the maximum y-value of the graph
    b
        defines the steepness
    c (x0)
        defines the symmetry center/peak of the graph
    """
    assert 0 < c_m <= 1
    assert 0 < b, "b must be greater than 0"
    max_float = sqrt(sys.float_info.max)

    @njit
    def f(x):
        # instead of try-except OverflowError
        if (x-c) > max_float:
            return 0
        else:
            o = (x - c)**2
        return c_m * exp(-b * o)
    return f

This function is now 3 times as fast as the original one. The original is 3 times as fast as the scikit-fuzzy equivalent.

@amogorkon
Copy link
Collaborator Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant