CalebBell / chemicals

chemicals: Chemical database of Chemical Engineering Design Library (ChEDL)
MIT License
186 stars 36 forks source link

Numba interface #11

Closed CalebBell closed 3 years ago

CalebBell commented 4 years ago

Hi Yoel,

Per our discussion, I have created a tentative wrapper around the functions in the fluids library and the chemicals library. Numba is never going to work perfectly for all cases, and optimizations to make numba perfect sometimes make CPython and especially PyPy slower. However I am very interested in it for what uses it can have. All I am looking to target with numba at this point is computation functions, not lookup functions. It seems anything with a dictionary or a string is probably going to get slower as well, often not possible to compile.

I believe you have more experience with it than I do, so I especially welcome your thoughts.

Here are some examples of the current wrapper. There are a few more tests.

import chemicals.numba
import chemicals.numba_vectorized

chemicals.numba.Antoine(100.0, 8.7687, 395.744, -6.469) # speed

chemicals.numba_vectorized.Psat_IAPWS(np.linspace(100.0,200.0, 3)) # vectorized computation speed

I find that a function has to be pretty slow to benefit from a single-point numba version of a function; but for functions with vector arguments, at 10 elements it is already showing significantly improved performance, compared to numpy also.

I think for some applications numba really wants a custom function as well, not to wrap the existing one. The optimizations of e.g.


def zs_to_ws(zs, MWs):
    cmps = range(len(zs))
    ws = [zs[i]*MWs[i] for i in cmps]
    Mavg = 0.0    # Cannot use sum and list comprehension with numba; otherwise Mavg = 1.0/sum(ws)
    for v in ws:
        Mavg += v
    Mavg = 1.0/Mavg
    for i in cmps:
        ws[i] *= Mavg
    return ws

Make the numba function return a list, but what we want there is a numpy array. It is not as elegant but I have no problem writing a few duplicate functions.

@numba.njit
def zs_to_ws(zs, MWs):
    ws = zs*MWs
    Mavg = 1.0/np.sum(ws)
    ws *= Mavg
    return ws 

It seems some functions need to be re-written in a more verbose way to work with numba also, for example https://github.com/CalebBell/fluids/blob/master/fluids/friction.py#L2917

I consider this all pretty preliminary.

yoelcortes commented 4 years ago

Thanks for sharing. I actually implemented njit to all functions previously in thermosteam, but as you may have noticed, there is not much benefit to adding this in small scalar functions... the compiling time overshadows this minor benefit. Numba becomes significantly worse with string comparisons. Scalar comparisons are OK, but there is no gain here either. Where numba really makes a huge difference is when working with arrays and for loops.

I think the best numba application examples I can think of are activity coefficients and vapor fraction functions. Here is one benchmark:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol', 'Methanol', 'Glycerol'])
>>> chemgroups = [i.Dortmund for i in chemicals]
>>> x = [0.25, 0.25, 0.25, 0.25]
>>> T = 350
>>> # In all fairness, creating an activity coefficient object takes 82 µs.
>>> f_gamma = tmo.equilibrium.DortmundActivityCoefficients(chemicals)
>>> %timeit f_gamma(x, T)
81.1 µs ± 562 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> tmo.speed_up() # Now with njit
>>> f_gamma(x, T) # Run once to compile
array([1.985, 1.303, 0.886, 1.148])
>>> %timeit f_gamma(x, T)
19.4 µs ± 542 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Now comparing with thermo:

>>> from thermo.unifac import DOUFIP2006, DOUFSG, UNIFAC
>>> UNIFAC(T, x, chemgroups, cached=None, subgroup_data=DOUFSG, interaction_data=DOUFIP2006, modified=True)
[1.9847323362236762,
 1.3031386940528067,
 0.8862784271005189,
 1.1484419017704377]
>>> %timeit UNIFAC(T, x, chemgroups, cached=None, subgroup_data=DOUFSG, interaction_data=DOUFIP2006, modified=True)
233 µs ± 53 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

I think you just have to divide the functions with if statements into small functions that you can njit (if they are worth it).

In BioSTEAM's ShortcutColumn, I actually njited all objective functions and passed it to an njited solver (flexsolve.fast.IQ_interpolation). It worked pretty well. I don't document the smaller functions because I feel that the long names I give them is good enough and leave the heavy documentation to the main interface (a class or higher level function)

yoelcortes commented 4 years ago

I would deprecate the functions that return lists. In the end, the bigger the lists, the more benefit to replacing them with arrays. I think it also makes the code much more concise and easier to read.If you compare activity_coefficients.py with thermo's code, I think the gains are clear.

yoelcortes commented 4 years ago

One more comment on using numba for for-loops, you may consider having fastmath=True and look into using prange (numba's parallel version of range). But this would require you to split your functions even further to only use fastmath in the applicable scenarios.

https://numba.pydata.org/numba-doc/latest/user/performance-tips.html

yoelcortes commented 4 years ago

Ahh, one last point on using numba... njit compiling small functions does become useful in the long run so you can use them in bigger njit compiled functions (since we can't use numpy functions within an njited function) .

CalebBell commented 4 years ago

Hi Yoel, Thanks for the feedback! That is one very impressive UNIFAC calculation speed. It seems there is a good reason to be able to numba compile as many functions as possible then. I am generally pleased with numba. Did you know you can cache the compiled code of numba now? Fastmath is really cool, although I can tell you in my professional experience using it for long enough inevitably leads to some weird floating-point bug occuring and it gets turned off.

I agree numpy can make code much more readable, which contributes to being easy to maintain and improve developer productivity.

However, I have a lot of personal simulations - as least as much code as in thermo/chemicals - written that depends on thermo/chemicals, and some of it I have worked very hard to optimize the speed of because it takes a long time to run otherwise. The optimizer I have been using is PyPy. PyPy is older than numba, and doesn't have some of numba's drawbacks. One of its drawbacks however is that it does not play well with compiled code; so to make it run fast, I have made sure numpy is not used in most of thermo/chemicals. I need to maintain that speed, so I need to leave the default implementations of all methods as pure-Python.

Please respect this preference; it made sense to go all-in for numba for you, starting when you did; but for me with lots of legacy code, PyPy is the smart choice, but I want to keep my options open to use Numba in the future as well.

Equally importantly, I don't know if either PyPy or Numba will continue to be developed and supported in the future. Both have some but only a little commercial support. Focusing on keeping CPython performant (without numpy) seems like the safest bet to me. If another optimizer option occurs (I have tentatively ported parts of the library to C myself to experiment, and used Nuitka as well), I would like to be able to try that out.

As a side note, you taught me today that in Python 3, all classes are new-style classes. It took me a while to figure it out!

yoelcortes commented 4 years ago

Yeah, I was low-key hoping we could focus on arrays for mixture stuff jaja. Still, I'm here to support and help out wherever I can. We can implement both python only and numpy-oriented code whenever there is a need. And you do have very valid reasons to support both. Either way, I got your back!

It would be nice to start having a convention for array-oriented vs list-oriented functions. Maybe... ws_to_zs and ws_to_zs_arr? We can think about this one later tho. If only Python had Julia's multiple-dispatch paradigm, then all this naming convention madness would end.

BTW, I had some problem caching the compiled code in numba whenever I changed a module. This may be a pretty good option once everything is set, but if any changes occur I get an error than only resolves itself once I delete the file cache.

CalebBell commented 4 years ago

Hi Yoel,

I have launched my first public interface to numba, for my fluids project in a new 0.1.82 release. https://fluids.readthedocs.io/fluids.numba.html Please feel free to try it out and share any thoughts you have!

I feel fairly experienced with numba now. It will probably take some time before I work on adding the same interface the ht library.

I have been able to convert most of the functionality of the library to use numba. Apart for a few things the performance has turned out well. Some of the drawbacks - like string inputs and function pointers - can end up not being as slow as they first appear; if a string entirely in a njit loop or hardcoded in a function and never changed numba seems to handle that well.

I have used a number of tricks, but the most versatile one ended up being to re-executed code with almost "macros". You can see an example here; https://github.com/CalebBell/fluids/blob/master/fluids/compressible.py#L839

The core idea is to keep the numba-compatibility code in the same place as the function, but have it be ignored (unless loading a numba interface). I did a re regular expressions to make the changes. Right now that could only work for functions, but it can probably be extended to work for classes also.

I do have the minimum numba version at 0.50; it seems to be evolving quickly and the idea of keeping the latest numba version as a optional dependency might be a good one.

yoelcortes commented 4 years ago

Sounds nice! I'll try it out. ~I never new about those macros, impressive stuff.~ I just had a closer look at fluids.numba, and realized you made the marcos yourself, nice. I have a couple of questions:

When you import numba from fluids, do you replace the function (in the original module) with the compiled one? If this would be done, it would enable us to create higher level functions that depend on numba compiled ones much more easily, increasing both speed and flexibility. This is what is currently done thermosteam/biosteam and flexsolve. The only detail we need to keep in mind, is to always import the module, and not the function, for example:

from some_package import some_module
from numba import jit

@jit
def some_function(x, y):
    return some_module.funciton(y) + x

We also wouldn't need to import all functions from a "numba" module, but just only run the module to get the requested behavior. I think this would allow users to switch more easily between non-compiled and compiled functions instead of having to change their imports.

I'm actually running into this problem: some functions imported from fluids in chemicals are not njit compiled, but would have to be in order to be used by higher level functions in chemicals. I am able to create jitclasses for Zabransky classes, but their dependence on polylog2 and horner doesn't let me use these methods.

At the current implementation of the numba interface, we would have to create two versions of these classes, one without numba that will be in the heat_capacities module, and one with numba that will be in the chemicals.numba module which uses the jit compiled functions in fluids. I haven't had a good look at it, but can you confirm that horner and polylog2 are also in fluids.numba?

I agree with you on using the latest numba version, I do so too. They are really making progress on allowing more and more python. When I started with numba, so many things were not allowed.

P.S. Sorry I haven't made as much progress in the chemicals package as I'd like to. I just had to get a couple of research milestones done.

CalebBell commented 4 years ago

Hi Yoel,

The implementation in fluids is based on re-loading, into a new namespace, all of the module files in fluids. The existing fluids modules are left untouched by design.

A few notable parts of the step:

Numba is not the wrapper in fluids. fluids also provides the fluids.units interface, and fluids.vectorized based on numpy's np.vectorize. Numba is also split into two interfaces - numba, and numpy ufuncs. For those reason, I'm not sold on switching all the functions in the library over; then the original versions would be unavailable and the other back ends would be broken.

For a user package to switch backends, it only needs to do something like: fluids = fluids.numba They can also obtain a deeper change by doing sys.modules['fluids'] = fluids.numba.

I can see the convenience of swapping modules, but I am counting on the modules being separate for functionality like tests that compare the output of the two functions: https://github.com/CalebBell/fluids/blob/master/tests/test_numba.py#L225

horner was available in fluids.numba.numerics before; I added polylog2 tonight but only to a master branch not a release.

The jitclass looks very promising! A PiecewiseHeatCapacity class? Fancy! Can we allow it to extrapolate also? I added to the tests the results with the gas constant change that I already, but they don't pass because extrapolation isn't allowed.

yoelcortes commented 4 years ago

I see, I think I understand the numba module more clearly. User imports are not a problem at all with this setup. But let me see if I got this correct:

This seems workable, but I have a couple of worries on using the high level code you've developed for numba support on the long term:

In any case, although there may not be an ideal solution to the numba API, I think its worth discussing some options to make it better on the long term. You probably thought of this already, but how would you feel about having a subpackage (a numba subfolder with modules that support numba) instead of just one module? For each module (e.g. heat_capacity.py) we could use some of the high level code that you've developed in fluids.numba regarding macros and converting types to arrays, jit compile the functions that don't need the macros directly from the original module, import the jit compiled functions we need from other chemicals.numba modules, and create the new code required for compatibility. Creating new modules would allow us to extend the original package for numba more explicitly. On the downside, we may not be able to use all the code you originally developed, this will take some time to put together, and it makes room for disregarding code reuse.

What do you think about this idea? If you are willing to give this a try in the future, I could get a head start once we are done with all the modules in chemicals. I think this would help me develop code using numba more easily (I'm planning on being actively involved!). I know that what you have now seems solid for the numba API, so if you prefer to pass on this idea, that's completely understandable too jaja.

Glad you liked the PiecewiseHeatCapacity. I can look into adding the extrapolation option possibly next week.

I hope my comments can be useful in guiding the project, Thanks for sharing this with me!

CalebBell commented 4 years ago

Hi Yoel, I have responded to your concerns in-line for clarity:

When we would like make changes for numba compatibility, we can just make this switch in the "IS_NUMBA" block. Correct We can adjust code with macros and try-except blocks to make them compatible with numba. Correct We can make new code in the numba.py module for any difficult cases. Correct No need to worry about the use of functions within functions as a completely new module is created with all __funcs as part of their namespace. Correct

Exceptions to the "one size fits all" can stack up (making the numba module too large)

Yep. I am very open to having more files and/or a numba_interface folder but for now let's keep it in the same file and try to be clear about where the lines may be drawn in the future.

When there is enough junk for 3 or 4 numba-specific files I think that would be a good time to revisit the issue and make the change if needed. Right now it feels like it will just add overhead in my mind but I agree it may be needed.

The IS_NUMBA sections may become big in some modules. Also, when making improvements, we will need to worry about both the numba and non-numba sections as one whole.

Yep. It already sucks :(. It sucks less than complete code duplication though.

The addition of try-except blocks will make both original functions and numba compilled functions slower... although only significant for small functions.

The macro can be used to avoid this problem; so far I haven't needed to slow anything down, and the module level try/except IS_NUMBA block only fails once per block while loading. Future less-experienced collaborators may feel less motivated to contribute when they read code that has been patched up for numba.

Fair point; the macros and having a second implementation are things I hope to avoid as much as possible. Most of the time it seems possible to code to support PyPy, Numba, and CPython very well.

The high level code is not too explicit and possibly hard to debug in the future. I'm sure you could handle it easily tho.

It will get better in time; for now the only tests that matter to me are the functions running with numba and getting the same result. The only happy thing about numba for is is that it is fast, the rest of it sucks.

I hope to keep the numba and regular code the mostly same, and in one place. This means the documentation is in one place, and the function signature, and when editing the function you don't have to switch between different files. But this will not always be possible and this is all very experimental :) Still, I am quite please with all the things I have already made work.

yoelcortes commented 4 years ago

Sounds good! I'll see how I add the new jitclasses over the weekend, I'll probably try the macro to remove the slots and repr methods, hopefully have less code duplication. Thanks!

CalebBell commented 4 years ago

Hi Yoel,

I wanted to share a fun example of what fastmath in numba can do. The following snipped should be 1, but it is not in fastmath mode.

from math import log

import numba

@numba.njit(fastmath=True)
def Campbell_Thodos(T, Tb, Tc, Pc, M, dipole=None, hydroxyl=False):
    R = 8.31446261815324
    Tc_inv = 1.0/Tc
    Tr = T/Tc
    Tbr = Tb/Tc
    Pc = Pc/101325.
    s = Tbr*log(Pc)/(1.0 - Tbr)
    Lambda = Pc**(1.0/3.)/(M**0.5*Tc**(5/6.))
    alpha = 0.3883 - 0.0179*s
    beta = 0.00318*s - 0.0211 + 0.625*Lambda**(1.35)
    if dipole is not None:
        theta = Pc*dipole*dipole/(Tc*Tc)
        alpha -= 130540 * theta**2.41
        beta += 9.74E6 * theta**3.38

    Zra = alpha + beta*(1.0 - Tr)
    print(1.0 + (1.0 - Tr)**(2.0/7.))
    Vs = R*Tc/(Pc*101325.0)*Zra**(1.0 + (1.0 - Tr)**(2.0/7.))
    return Vs

kwargs = dict(T=405.45, Tb=239.82, Tc=405.45, Pc=111.7*101325, M=17.03, dipole=1.47, hydroxyl=False)
Campbell_Thodos(**kwargs)/7.347366126245346e-05

The exact issue is the term (1.0 + (1.0 - Tr)**(2.0/7.)) which normally evaluates to 1 for Tr = 1 but is 1.0000276404926838 in fastmath mode. I don't have any answers for now. I added a check for the Tr ratio for this function to make it work in fastmath mode, but that slows the code down minutely as well. fastmath is on right now. It would be nice to keep it on. It is also really cool that with numba it can be on/off with a per function basis, which is a possibility that doesn't exist in most other code bases.

This was the last issue with volume that I am aware of.

CalebBell commented 4 years ago

I also want to share what is currently the biggest drawback for me with numba. Even with caching on, the first call on each Python load to a function being compiled takes forever.

For that simple function above, I took the following timings:

Compile fresh: 350 ms; compile with cache on: 250 ms Compile fresh with type signature: about 100 us with caching on and off, oddly

It seems providing the type signature makes all the difference. I am hoping to leverage a combination of tools to get the type signatures for each method, and provide them automatically.

yoelcortes commented 4 years ago

Thanks for sharing the fastmath stuff. Maybe a small fix with something like if 1.0 < Tr < 1.0001: Tr = 1.0 may be OK?... But tbh, I would rather not use fastmath in any case that numerical accuracy is not maintained. This would probably lead to numerical errors down the line with solvers and higher level analysis that are difficult to deal with.

Yeah, compiling code takes very long, this is why I never added it in all the pure component chemical properties... the trade-off wasn't that good, but its great that you're looking into this. In biosteam, compiling all the numba code takes a good 15 seconds, but its worth it when evaluating grids of biorefinery designs and doing high level analysis (cuts the time in more that half!).

I'd like to share with you some cool things you can do with numba's overload and register_jitable. You can add flexible numba functionality to functions without having to compile anything, their "overloaded" and "jitted" versions are only active when the function is used in jit compiled code. It works really well in flexsolve. I made everything "registered_jitable" and all the solvers can be used within jit-compiled functions:

Example for overload: https://github.com/yoelcortes/flexsolve/blob/master/flexsolve/utils.py

Example use (no need to run flexsolve.speed_up):

>>> from numba import njit
>>> import flexsolve as flx
>>> f = njit(lambda x: x**3 - 40 + 2*x) # Must be jit compiled to run in other compiled code
>>> # For demonstration purposes, the high level compiled function is a silly one liner
>>> solve_x = njit(lambda: flx.IQ_interpolation(f, -5., 5.))
>>> x = solve_x() # First run is slow because it needs to compile
>>> %timeit solve_x()
1.07 µs ± 40.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

For comparison without numba, solving took 30 us. Other solvers like secant just take hundreds of nanosecods to finish when jit compiled, so I'm pretty happy with that

yoelcortes commented 4 years ago

By the way, I added the macro to make jitclasses for heat capacity. I still need to add the extrapolation, I just realized I forgot about it jaja.

yoelcortes commented 4 years ago

I just added the extrapolation and made sure that the heat capacity classes satisfied tests. I always prefer methods without too many "if" behaviors because make for more light-weight code and are more explicit to work with, so I made "force" methods that extrapolate. These force methods are used in the tests.

yoelcortes commented 4 years ago

Hi Caleb,

Just FYI, I tried implementing the function signature to thermosteam's activity coefficient functions, and I found problems in contiguous vs non-contiguous arrays and a couple of warnings regarding the @ operator being slow with non-contiguous arrays (which is pretty annoying considering I use both the transpose and non-transposed versions). I'm not sure what kind of magic does numba do at compilation time to find good signatures, but I think manually giving signatures to functions that use 2d arrays can be inconvenient.

In the end I found that enabling cache was the simple I blessing needed! I'm not sure why I had problems with this before, but my compilation time decreased from ~10 to ~2 seconds! I believe I had such a large compilation time because of the functions that use 2d arrays, which may be harder to compile for numba... but I haven't confirmed this.

CalebBell commented 4 years ago

Nice work Yoel!

I will keep in mind that 2d and higher will likely be a substantial challenge. Numba allows providing multiple signatures, so I will plan to include this functionality to handle different array orders. Signatures which never get used never get compiled, so simply providing multiple combinations may work. That's a really impressive numba load time! By comparison, one of the functions in chemicals currently takes about 8 seconds to compile. When precompilled, it seems to take 6 ms for the first call; still much better though.

import time
from math import log
import numba
@numba.njit(cache=True)
def BVirial_Tsonopoulos_extended(T, Tc, Pc, omega, a=0, b=0, species_type='', 
                                 dipole=0, order=0):
    R = 8.31446261815324
    Tr = T/Tc
    if order == 0:
        B0 = 0.1445 - 0.33/Tr - 0.1385/Tr**2 - 0.0121/Tr**3 - 0.000607/Tr**8
        B1 = 0.0637 + 0.331/Tr**2 - 0.423/Tr**3 - 0.008/Tr**8
        B2 = 1./Tr**6
        B3 = -1./Tr**8
    elif order == 1:
        B0 = 33*Tc/(100*T**2) + 277*Tc**2/(1000*T**3) + 363*Tc**3/(10000*T**4) + 607*Tc**8/(125000*T**9)
        B1 = -331*Tc**2/(500*T**3) + 1269*Tc**3/(1000*T**4) + 8*Tc**8/(125*T**9)
        B2 = -6.0*Tc**6/T**7
        B3 = 8.0*Tc**8/T**9
    elif order == 2:
        B0 = -3*Tc*(27500 + 34625*Tc/T + 6050*Tc**2/T**2 + 1821*Tc**7/T**7)/(125000*T**3)
        B1 = 3*Tc**2*(331 - 846*Tc/T - 96*Tc**6/T**6)/(500*T**4)
        B2 = 42.0*Tc**6/T**8
        B3 = -72.0*Tc**8/T**10
    elif order == 3:
        B0 = 3*Tc*(8250 + 13850*Tc/T + 3025*Tc**2/T**2 + 1821*Tc**7/T**7)/(12500*T**4)
        B1 = 3*Tc**2*(-662 + 2115*Tc/T + 480*Tc**6/T**6)/(250*T**5)
        B2 = -336.0*Tc**6/T**9
        B3 = 720.0*Tc**8/T**11
    elif order == -1:
        B0 = 289*T/2000. - 33*Tc*log(T)/100. + (969500*T**6*Tc**2 + 42350*T**5*Tc**3 + 607*Tc**8)/(7000000.*T**7)
        B1 = 637*T/10000. - (23170*T**6*Tc**2 - 14805*T**5*Tc**3 - 80*Tc**8)/(70000.*T**7)
        B2 = -Tc**6/(5*T**5)
        B3 = Tc**8/(7*T**7)
    elif order == -2:
        B0 = 289*T**2/4000. - 33*T*Tc*log(T)/100. + 33*T*Tc/100. + 277*Tc**2*log(T)/2000. - (254100*T**5*Tc**3 + 607*Tc**8)/(42000000.*T**6)
        B1 = 637*T**2/20000. - 331*Tc**2*log(T)/1000. - (44415*T**5*Tc**3 + 40*Tc**8)/(210000.*T**6)
        B2 = Tc**6/(20*T**4)
        B3 = -Tc**8/(42*T**6)
    else: 
        raise ValueError('Only orders -2, -1, 0, 1, 2 and 3 are supported.')
    if a == 0 and b == 0 and species_type != '':
        if species_type == 'simple' or species_type == 'normaly':
            a, b = 0, 0
        elif species_type == 'methyl alcohol':
            a, b = 0.0878, 0.0525
        elif species_type == 'water':
            a, b = -0.0109, 0
        elif dipole != 0 and Tc != 0 and Pc != 0:
            dipole_r = 1E5*dipole**2*(Pc/101325.0)/Tc**2

            if (species_type == 'ketone' or species_type == 'aldehyde'
            or species_type == 'alkyl nitrile' or species_type == 'ether'
            or species_type == 'carboxylic acid' or species_type == 'ester'):
                a, b = -2.14E-4*dipole_r-4.308E-21*dipole_r**8, 0
            elif (species_type == 'alkyl halide' or species_type == 'mercaptan'
            or species_type == 'sulfide' or species_type == 'disulfide'):
                a, b = -2.188E-4*dipole_r**4-7.831E-21*dipole_r**8, 0

            elif species_type == 'alkanol':
                a, b = 0.0878, 0.00908+0.0006957*dipole_r
    Br = B0 + omega*B1 + a*B2 + b*B3
    return Br*R*Tc/Pc

a = time.time()
BVirial_Tsonopoulos_extended(430., 405.65, 11.28E6, 0.252608, a=0, b=0, species_type='ketone', dipole=1.469)
time.time() - a

I don't have an immediate time frame for getting the signatures automatically included though. I am close to turning caching on by default in fluids.

CalebBell commented 4 years ago

OK, I think I was getting confused with my timings a little. It seem the very first call to numba's jit does some delayed-loading of its own, which takes about 1.5 seconds for me. As I was isolating trying to time only one function at a time, I was getting that delay.

Additionally I didn't realize that then the type signature is provided, the function gets compiled then and there, not when it's first used.

Putting all this together, there does not seem to be a performance improvement to providing the type signature. Setting cache to True is all we need, indeed, as you found yourself.

CalebBell commented 4 years ago

To date the list of functions not working is: