Skip to content

Commit

Permalink
Remove the Numba JIT-compilation code (#1414)
Browse files Browse the repository at this point in the history
The JIT-compiled code time savings don't pay back the compilation time in Python 3.9+, so drop the library dependency. See #1400 and #1413, which improved the payback in some cases but not enough to matter to runSim.

See https://github.com/1fish2/numba-test and https://github.com/1fish2/numba-test/blob/main/observations.md for measurements of 4 matrices captured from a cell sim which run about this many times in a typical cell generation:

* TwoComponentSystem derivatives: 261K
* TwoComponentSystem derivatives_jacobian: 32K
* Equilibrium rates: 267K
* Equilibrium rates_jacobian: 59K

### Changes

* Remove `numba` from `requirements.txt` but I'll leave it in the `wcEcoli3` pyenv on Sherlock until next time we need to rebuild it.
* Update the --jit / --no-jit CLI arg docstrings, but leave in these args so no shell scripts will break.
* Don't change the `build_ode.build_functions()` API or its callers, so it'd be easy to reenable the JIT compiler someday.
  • Loading branch information
1fish2 authored Dec 1, 2023
1 parent ff20770 commit 576d3b3
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 30 deletions.
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ wheel>=0.40.0
numpy==1.24.3
scipy==1.10.1

numba==0.57.0
llvmlite==0.40.0

aesara==2.9.0
Expand Down
4 changes: 2 additions & 2 deletions runscripts/fireworks/fw_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@
if charging creates a large update to improve stability of sims
LOG_TO_DISK_EVERY (int, "1"): frequency at which simulation outputs are
logged to disk
JIT (int, "0"): if nonzero, jit compiled functions are used for certain
processes, otherwise only uses lambda functions
JIT (int, "0"): NO-OP (This used to select jit-compiled functions in certain
processes)
Modeling options:
MASS_DISTRIBUTION (int, "1"): if nonzero, a mass coefficient is drawn from
Expand Down
11 changes: 3 additions & 8 deletions wholecell/tests/utils/test_library_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,8 @@ def time_it(code_to_measure, title='Measured'):

# Originally generated by the parameter calculator (parca) into
# reconstruction/ecoli/dataclasses/process/two_component_system_odes_parca.py
#
# TODO(jerry): Use smaller matrices for this test.
#
# TODO(jerry): In the original parca-generated code, remove the `**1.0`
# terms. In both, factor out common subexpressions and constants?
#
# TODO(jerry): Try JIT-compiling this with Numba.
def derivatives(y, t):
_ = t
return np.array([
[-100000000.0*y[0]*y[6] + 500.0*y[3]*y[4]], [0], [0],
[100000000.0*y[0]*y[6] - 500.0*y[3]*y[4]], [0],
Expand Down Expand Up @@ -123,6 +117,7 @@ def derivatives(y, t):

# Ditto.
def derivatives_jacobian(y, t):
_ = t
return np.array([
[-100000000.0*y[6], 0, 0, 500.0*y[4], 500.0*y[3], 0, -100000000.0*y[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
Expand Down Expand Up @@ -242,7 +237,7 @@ def test_odeint(self):
y0 = np.random.random(41)

def odeint():
y = scipy.integrate.odeint(
_ = scipy.integrate.odeint(
derivatives, y0, t=[0, 1e6], Dfun=derivatives_jacobian,
mxstep=10000)
self.time_this(odeint, 0.4)
Expand Down
31 changes: 14 additions & 17 deletions wholecell/utils/build_ode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,49 @@
"""

import numpy as np
from numba import njit
from sympy import Matrix
from typing import Any, Callable, Tuple


def build_functions(arguments, expression):
# type: (str, str) -> Tuple[Callable, Callable]
"""Build a function from its arguments and source code expression and set
up Numba to JIT-compile it on demand. There will be overhead to compile
the first time the jit version is called so two functions are returned and
can be selected for optimal performance.
"""Build a function from its arguments and source code expression.
(This USED TO return a second function that had Numba set up to JIT-compile
the code on demand, but the compiled code time savings don't pay back the
compilation time in Python 3.9+.)
Numba will optimize expressions like 1.0*y[2]**1.0 while compiling it
to machine code.
This still returns two functions so the JIT compiler could be restored
someday.
Args:
arguments (str): comma-separated lambda argument names
expression (str): expression to compile
Returns:
a lambda function(arguments)
a Numba Dispatcher function(arguments)
a function(arguments),
the same function(arguments) [was a JIT-enabled version]
"""
local_dict: dict[str, Any] = {}
expression = f'def f({arguments}):\n' + expression
exec(expression, globals(), local_dict)
f = local_dict['f']

# Too bad cache=True doesn't work with string source code.
f_jit = njit(f, error_model='numpy')

return f, f_jit
return f, f


def _matrix_to_array(matrix):
# type: (Matrix) -> str
"""Convert a sympy Matrix expression to a function literal."""
"""Convert a sympy Matrix expression to a function body."""
rows, cols = matrix.shape
_ = np # So the tools won't warn about unused np import.

function_str = f' arr = np.zeros(({rows}, {cols}))\n'
function_str = f'\tarr = np.zeros(({rows}, {cols}))\n'
for i in range(rows):
for j in range(cols):
if matrix[i, j] != 0:
function_str += f' arr[{i}, {j}] = {matrix[i, j]}\n'
function_str += f'\tarr[{i}, {j}] = {matrix[i, j]}\n'

function_str += ' return arr'
function_str += '\treturn arr'
return function_str


Expand Down
3 changes: 1 addition & 2 deletions wholecell/utils/scriptBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,7 @@ def add_bool_option(name, key, help):
help='if true, adjusts the timestep if charging creates a large'
' update to improve stability of sims')
add_bool_option('jit', 'jit',
help='If true, jit-compiled functions are used for certain'
' processes, otherwise only uses lambda functions')
help='NO-OP (This used to select jit-compiled functions in certain processes)')
add_bool_option('mass_distribution', 'massDistribution',
help='If true, a mass coefficient is drawn from a normal distribution'
' centered on 1; otherwise it is set equal to 1')
Expand Down

0 comments on commit 576d3b3

Please sign in to comment.