From b35069ad4a4cf07b8c73335dc15c6e8e02919e60 Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 09:03:01 -0500 Subject: [PATCH 01/10] shift random choices out to numba funcs --- pyDeltaRCM/utils.py | 62 +++++++++++++++++++++++++++++++++++++++ pyDeltaRCM/water_tools.py | 21 +++++++------ 2 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 pyDeltaRCM/utils.py diff --git a/pyDeltaRCM/utils.py b/pyDeltaRCM/utils.py new file mode 100644 index 00000000..948dc68e --- /dev/null +++ b/pyDeltaRCM/utils.py @@ -0,0 +1,62 @@ + +import numpy as np + +import time +import numba + +# utilities used in various places in the model and docs + + +@numba.njit +def random_pick(probs): + """ + Randomly pick a number weighted by array probs (len 8) + Return the index of the selected weight in array probs + """ + + # catch bad prob arrays + if np.nansum(probs) == 0: + probs[1, 1] = 0 + for i, prob in np.ndenumerate(probs): + if np.isnan(prob): + probs[i] = 1 + + # prep array + for i, prob in np.ndenumerate(probs): + if np.isnan(prob): + probs[i] = 0 + + cutoffs = np.cumsum(probs) + v = np.random.uniform(0, cutoffs[-1]) + idx = np.searchsorted(cutoffs, v) + + return idx + + +@numba.njit +def random_pick_inlet(choices, probs=None): + """ + Randomly pick a number from array choices weighted by array probs + Values in choices are column indices + + Return a tuple of the randomly picked index for row 0 + """ + + if probs is None: + probs = np.array([1. for i in range(len(choices))]) + + cutoffs = np.cumsum(probs) + # print(cutoffs.dtype) + v = np.random.uniform(0, cutoffs[-1]) + # print("TYPES:", cutoffs.dtype, type(v)) + idx = np.searchsorted(cutoffs, v) + + return choices[idx] + + +def _get_version(): + """ + Extract version number from single file, and make it availabe everywhere. + """ + from . import _version + return _version.__version__() diff --git a/pyDeltaRCM/water_tools.py b/pyDeltaRCM/water_tools.py index 6dd86ea3..5a770daf 100644 --- a/pyDeltaRCM/water_tools.py +++ b/pyDeltaRCM/water_tools.py @@ -6,6 +6,8 @@ import logging import time +import numba + from math import floor, sqrt, pi import numpy as np from random import shuffle @@ -19,6 +21,7 @@ from netCDF4 import Dataset from .shared_tools import shared_tools +from . import utils # tools for water routing algorithms @@ -142,8 +145,8 @@ def init_water_iteration(self): def run_water_iteration(self): - iter = 0 - start_indices = [self.random_pick_inlet( + _iter = 0 + start_indices = [utils.random_pick_inlet( self.inlet) for x in range(self.Np_water)] self.qxn.flat[start_indices] += 1 @@ -154,11 +157,11 @@ def run_water_iteration(self): self.looped[:] = 0 - while (sum(current_inds) > 0) & (iter < self.itmax): + while (sum(current_inds) > 0) & (_iter < self.itmax): - iter += 1 + _iter += 1 - self.check_size_of_indices_matrix(iter) + self.check_size_of_indices_matrix(_iter) inds = np.unravel_index(current_inds, self.depth.shape) inds_tuple = [(inds[0][i], inds[1][i]) @@ -176,13 +179,13 @@ def run_water_iteration(self): new_inds = np.array(new_inds, dtype=np.int) new_inds[np.array(dist) == 0] = 0 - self.indices[:, iter] = new_inds + self.indices[:, _iter] = new_inds - current_inds = self.check_for_loops(new_inds, iter) + current_inds = self.check_for_loops(new_inds, _iter) current_inds = self.check_for_boundary(current_inds) - self.indices[:, iter] = current_inds + self.indices[:, _iter] = current_inds current_inds[self.free_surf_flag > 0] = 0 @@ -422,7 +425,7 @@ def get_weight(self, ind): self.weight = depth_ind ** self.theta_water * self.weight self.weight[depth_ind <= self.dry_depth] = np.nan - new_cell = self.random_pick(self.weight) + new_cell = utils.random_pick(self.weight) return new_cell From 32611709cc55340c0dfe8cb385d4380bc74b8925 Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 09:03:24 -0500 Subject: [PATCH 02/10] use utils version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b80b09ee..088508b3 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ #! /usr/bin/env python from setuptools import setup, find_packages -from pyDeltaRCM import shared_tools +from pyDeltaRCM import utils setup(name='pyDeltaRCM', - version=shared_tools._get_version(), + version=utils._get_version(), author='The DeltaRCM Team', license='MIT', description="Python version of original Matlab DeltaRCM", From 7683df3d27db1613cf539eeb2d859afbf91e0fe4 Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 12:38:20 -0500 Subject: [PATCH 03/10] shift new_cells ops out to standalone jitted functions --- pyDeltaRCM/init_tools.py | 3 + pyDeltaRCM/utils.py | 4 +- pyDeltaRCM/water_tools.py | 132 ++++++++++++++++++++++++++------------ 3 files changed, 95 insertions(+), 44 deletions(-) diff --git a/pyDeltaRCM/init_tools.py b/pyDeltaRCM/init_tools.py index 70008773..614e4bfc 100644 --- a/pyDeltaRCM/init_tools.py +++ b/pyDeltaRCM/init_tools.py @@ -9,6 +9,7 @@ from math import floor, sqrt, pi import numpy as np from random import shuffle +import numba import matplotlib from matplotlib import pyplot as plt @@ -310,6 +311,8 @@ def create_domain(self): self.cell_type[:self.L0, channel_inds:y_channel_max] = cell_channel self.inlet = list(np.unique(np.where(self.cell_type == 1)[1])) + self.inlet_typed = numba.typed.List() + [self.inlet_typed.append(x) for x in self.inlet] self.eta[:] = self.stage - self.depth self.clim_eta = (-self.h0 - 1, 0.05) diff --git a/pyDeltaRCM/utils.py b/pyDeltaRCM/utils.py index 948dc68e..40c1bf42 100644 --- a/pyDeltaRCM/utils.py +++ b/pyDeltaRCM/utils.py @@ -30,7 +30,7 @@ def random_pick(probs): v = np.random.uniform(0, cutoffs[-1]) idx = np.searchsorted(cutoffs, v) - return idx + return int(idx) @numba.njit @@ -46,9 +46,7 @@ def random_pick_inlet(choices, probs=None): probs = np.array([1. for i in range(len(choices))]) cutoffs = np.cumsum(probs) - # print(cutoffs.dtype) v = np.random.uniform(0, cutoffs[-1]) - # print("TYPES:", cutoffs.dtype, type(v)) idx = np.searchsorted(cutoffs, v) return choices[idx] diff --git a/pyDeltaRCM/water_tools.py b/pyDeltaRCM/water_tools.py index 5a770daf..6a357d6d 100644 --- a/pyDeltaRCM/water_tools.py +++ b/pyDeltaRCM/water_tools.py @@ -26,6 +26,85 @@ # tools for water routing algorithms +@numba.njit +def _index_slicer(kernel, ind): + """Get a slice out of a kernel for an ind. + """ + + return kernel[ind[0] - 1 + 1:ind[0] + 2 + 1, ind[1] - 1 + 1:ind[1] + 2 + 1] + + +@numba.njit +def get_weights_kernels(ind, pad_stage, stage, distances, qx, qy, + ivec, jvec, pad_depth, pad_cell_type): + """Get weights and kernels for an ind. + """ + stage_ind = _index_slicer(pad_stage, ind) + weight_sfc = np.maximum(0, + (stage[ind] - stage_ind) / distances) + + weight_int = np.maximum(0, (qx[ind] * jvec + + qy[ind] * ivec) / distances) + depth_ind = _index_slicer(pad_depth, ind) + ct_ind = _index_slicer(pad_cell_type, ind) + + return weight_sfc, weight_int, depth_ind, ct_ind + + +@numba.njit +def get_new_cell(ind, weight_sfc, weight_int, depth_ind, ct_ind, + dry_depth, gamma, theta_water): + """Get new_cell based on weights and kernels for an ind. + """ + + if ind[0] == 0: + weight_sfc[0, :] = 0 + weight_int[0, :] = 0 + + for (i, u), (j, v) in zip(np.ndenumerate(depth_ind), + np.ndenumerate(ct_ind)): + if (u <= dry_depth) or (v == -2): + weight_sfc[i] = 0 + weight_int[i] = 0 + + if np.nansum(weight_sfc) > 0: + weight_sfc = weight_sfc / np.nansum(weight_sfc) + + if np.nansum(weight_int) > 0: + weight_int = weight_int / np.nansum(weight_int) + + _weight = gamma * weight_sfc + (1 - gamma) * weight_int + _weight = depth_ind ** theta_water * _weight + for i, u in np.ndenumerate(depth_ind): + if u <= dry_depth: + _weight[i] = np.nan + + new_cell = utils.random_pick(_weight) + + return new_cell + + +@numba.njit +def get_new_cells(inds_tuple_typed, pad_stage, stage, distances, qx, qy, + ivec, jvec, pad_depth, pad_cell_type, dry_depth, gamma, + theta_water): + """Get new_cells based on current model state. + """ + new_cells = [np.int(x) for x in range(0)] + for ind in inds_tuple_typed: + if ind != (0, 0): + weight_sfc, weight_int, depth_ind, ct_ind = get_weights_kernels( + ind, pad_stage, stage, distances, qx, qy, ivec, jvec, + pad_depth, pad_cell_type) + new_cells.append(get_new_cell(ind, weight_sfc, weight_int, + depth_ind, ct_ind, dry_depth, + gamma, theta_water)) + else: + new_cells.append(int(4)) + + return new_cells + + class water_tools(shared_tools): def update_flow_field(self, iteration): @@ -146,8 +225,9 @@ def init_water_iteration(self): def run_water_iteration(self): _iter = 0 - start_indices = [utils.random_pick_inlet( - self.inlet) for x in range(self.Np_water)] + typed_inlet = self.inlet_typed + start_indices = [utils.random_pick_inlet(typed_inlet) + for x in range(self.Np_water)] self.qxn.flat[start_indices] += 1 self.qwn.flat[start_indices] += self.Qp_water / self.dx / 2. @@ -164,11 +244,18 @@ def run_water_iteration(self): self.check_size_of_indices_matrix(_iter) inds = np.unravel_index(current_inds, self.depth.shape) + inds_tuple_typed = numba.typed.List() inds_tuple = [(inds[0][i], inds[1][i]) for i in range(len(inds[0]))] + [inds_tuple_typed.append(x) for x in inds_tuple] - new_cells = [self.get_weight(x) - if x != (0, 0) else 4 for x in inds_tuple] + new_cells = get_new_cells(inds_tuple_typed, self.pad_stage, + self.stage, self.distances, + self.qx, self.qy, + self.ivec, self.jvec, + self.pad_depth, self.pad_cell_type, + self.dry_depth, self.gamma, + self.theta_water) new_inds = list(map(lambda x, y: self.calculate_new_ind(x, y) if y != 4 else 0, inds_tuple, new_cells)) @@ -392,43 +479,6 @@ def calculate_new_ind(self, ind, new_cell): return new_ind_flat - def get_weight(self, ind): - - stage_ind = self.pad_stage[ - ind[0] - 1 + 1:ind[0] + 2 + 1, ind[1] - 1 + 1:ind[1] + 2 + 1] - - weight_sfc = np.maximum(0, - (self.stage[ind] - stage_ind) / self.distances) - - weight_int = np.maximum(0, (self.qx[ind] * self.jvec + - self.qy[ind] * self.ivec) / self.distances) - - if ind[0] == 0: - weight_sfc[0, :] = 0 - weight_int[0, :] = 0 - - depth_ind = self.pad_depth[ - ind[0] - 1 + 1:ind[0] + 2 + 1, ind[1] - 1 + 1:ind[1] + 2 + 1] - ct_ind = self.pad_cell_type[ - ind[0] - 1 + 1:ind[0] + 2 + 1, ind[1] - 1 + 1:ind[1] + 2 + 1] - - weight_sfc[(depth_ind <= self.dry_depth) | (ct_ind == -2)] = 0 - weight_int[(depth_ind <= self.dry_depth) | (ct_ind == -2)] = 0 - - if np.nansum(weight_sfc) > 0: - weight_sfc = weight_sfc / np.nansum(weight_sfc) - - if np.nansum(weight_int) > 0: - weight_int = weight_int / np.nansum(weight_int) - - self.weight = self.gamma * weight_sfc + (1 - self.gamma) * weight_int - self.weight = depth_ind ** self.theta_water * self.weight - self.weight[depth_ind <= self.dry_depth] = np.nan - - new_cell = utils.random_pick(self.weight) - - return new_cell - def build_weight_array(self, array, fix_edges=False, normalize=False): """ Create np.array((8,L,W)) of quantity a From 3cdcc5de120b48ee3ea0f1a83d8b37c0999adc7b Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 12:49:34 -0500 Subject: [PATCH 04/10] switch one random call in sed --- pyDeltaRCM/sed_tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyDeltaRCM/sed_tools.py b/pyDeltaRCM/sed_tools.py index ee5615f2..39931fe3 100644 --- a/pyDeltaRCM/sed_tools.py +++ b/pyDeltaRCM/sed_tools.py @@ -19,6 +19,7 @@ from netCDF4 import Dataset from .shared_tools import shared_tools +from . import utils # tools for sediment routing algorithms and deposition/erosion @@ -200,7 +201,7 @@ def sed_parcel(self, theta_sed, sed, px, py): if ind[0] == 0: weight[0, :] = np.nan - new_cell = self.random_pick(weight) + new_cell = utils.random_pick(weight) jstep = self.iwalk.flat[new_cell] istep = self.jwalk.flat[new_cell] From 0de5452dcf8b3ce1f0600e439f99f58a512ba870 Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 13:01:31 -0500 Subject: [PATCH 05/10] add a consistency check --- tests/test_output_consistency.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_output_consistency.py diff --git a/tests/test_output_consistency.py b/tests/test_output_consistency.py new file mode 100644 index 00000000..b946de33 --- /dev/null +++ b/tests/test_output_consistency.py @@ -0,0 +1,24 @@ +# unit tests for consistent model outputs + +import pytest + +import sys +import os +import numpy as np + +from pyDeltaRCM.deltaRCM_driver import pyDeltaRCM + +# need to create a simple case of pydeltarcm object to test these functions +delta = pyDeltaRCM(input_file=os.path.join(os.getcwd(), 'tests', 'test.yaml')) + + +def test_bed_after_one_update(): + """ + """ + delta.update() + # slice is: delta.eta[:4, 2:7] + _exp = np.array([[0., -1., -1., -1., 0.], + [-0.8469125, -0.82371545, -0.82951754, -0.909775, -0.9997975], + [-0.99919, -0.99838, -0.9981775, -0.9989875, -0.9997975], + [-1., -1., -1., -1., -1.]]) + assert np.all(delta.eta[:4, 2:7] == pytest.approx(_exp)) From 4a463ce8671cc3f0afedded168c19602e821e5a0 Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 13:21:53 -0500 Subject: [PATCH 06/10] add timer --- tests/time_water.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/time_water.py diff --git a/tests/time_water.py b/tests/time_water.py new file mode 100644 index 00000000..ecf04ca7 --- /dev/null +++ b/tests/time_water.py @@ -0,0 +1,39 @@ +import sys +import os +import time + +import cProfile +import pstats + +import numpy as np +import matplotlib.pyplot as plt + +from pyDeltaRCM.deltaRCM_driver import pyDeltaRCM + +# fix the random seed manually here, since we use the +# defaults for model instantiation +np.random.seed(0) +delta = pyDeltaRCM() + +# run these functions once so that numba can compile them to machine code +# this is a "startup cost", but it only happens once, +# then the benefit is reaped over every subsequent iteration. +delta.init_water_iteration() +delta.run_water_iteration() + +# begin timing, +# -- for ten iterations of the *whole* model update() +start = time.time() +for _t in range(0, 10): + print("timestep:", _t) + delta.update() +end = time.time() +# end timing + +print("Elapsed (without compilation) = %s min" % str((end - start) / 60)) + + +# run the profiler for one more update() +cProfile.run('delta.update()', '../temp/updatetime') +p = pstats.Stats('../temp/updatetime') +p.strip_dirs().sort_stats('cumtime').print_stats() From 01bfe2b3f77d11c42e6199cacf64b80d4203535f Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 13:42:58 -0500 Subject: [PATCH 07/10] add numba dep --- requirements.txt | 1 + setup.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 90684434..d60e3ff1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ scipy netCDF4 basic_modeling_interface pyyaml +numba diff --git a/setup.py b/setup.py index 088508b3..f3012f7f 100644 --- a/setup.py +++ b/setup.py @@ -13,5 +13,6 @@ include_package_data=True, url='https://github.com/DeltaRCM/pyDeltaRCM', install_requires=['matplotlib', 'netCDF4', - 'basic-modeling-interface', 'scipy', 'numpy', 'pyyaml'], + 'basic-modeling-interface', 'scipy', 'numpy', + 'pyyaml', 'numba'], ) From 9fda2c2454d18507e659840cb34bff125251b892 Mon Sep 17 00:00:00 2001 From: amoodie Date: Thu, 7 May 2020 21:17:48 -0500 Subject: [PATCH 08/10] set rng in numba, and make all calls to random in numba jitted functions --- pyDeltaRCM/init_tools.py | 6 +++++- pyDeltaRCM/sed_tools.py | 8 ++++---- pyDeltaRCM/utils.py | 10 ++++++++++ tests/test_yaml_parsing.py | 11 ++++++----- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pyDeltaRCM/init_tools.py b/pyDeltaRCM/init_tools.py index 614e4bfc..bc7fd204 100644 --- a/pyDeltaRCM/init_tools.py +++ b/pyDeltaRCM/init_tools.py @@ -23,6 +23,10 @@ import logging import time import yaml + +from . import utils + + # tools for initiating deltaRCM model domain @@ -98,7 +102,7 @@ def import_files(self): if self.seed is not None: if self.verbose >= 2: print("setting random seed to %s " % str(self.seed)) - np.random.seed(self.seed) + utils.set_random_seed(self.seed) def set_constants(self): diff --git a/pyDeltaRCM/sed_tools.py b/pyDeltaRCM/sed_tools.py index 39931fe3..b2fd2bb7 100644 --- a/pyDeltaRCM/sed_tools.py +++ b/pyDeltaRCM/sed_tools.py @@ -240,8 +240,8 @@ def sand_route(self): theta_sed = self.theta_sand num_starts = int(self.Np_sed * self.f_bedload) - start_indices = [self.random_pick_inlet( - self.inlet) for x in range(num_starts)] + typed_inlet = self.inlet_typed + start_indices = [utils.random_pick_inlet(typed_inlet) for x in range(num_starts)] for np_sed in range(num_starts): @@ -282,8 +282,8 @@ def mud_route(self): theta_sed = self.theta_mud num_starts = int(self.Np_sed * (1 - self.f_bedload)) - start_indices = [self.random_pick_inlet( - self.inlet) for x in range(num_starts)] + typed_inlet = self.inlet_typed + start_indices = [utils.random_pick_inlet(typed_inlet) for x in range(num_starts)] for np_sed in range(num_starts): diff --git a/pyDeltaRCM/utils.py b/pyDeltaRCM/utils.py index 40c1bf42..7f539a96 100644 --- a/pyDeltaRCM/utils.py +++ b/pyDeltaRCM/utils.py @@ -7,6 +7,16 @@ # utilities used in various places in the model and docs +@numba.njit +def set_random_seed(_seed): + np.random.seed(_seed) + + +@numba.njit +def get_random_uniform(N): + return np.random.uniform(0, 1, N) + + @numba.njit def random_pick(probs): """ diff --git a/tests/test_yaml_parsing.py b/tests/test_yaml_parsing.py index 67b6a6da..9ce61180 100644 --- a/tests/test_yaml_parsing.py +++ b/tests/test_yaml_parsing.py @@ -6,6 +6,7 @@ from pyDeltaRCM.deltaRCM_driver import pyDeltaRCM +from pyDeltaRCM.utils import set_random_seed, get_random_uniform # utilities for file writing def create_temporary_file(tmp_path, file_name): @@ -105,13 +106,13 @@ def test_random_seed_settings_value(tmp_path): p, f = create_temporary_file(tmp_path, file_name) write_parameter_to_file(f, 'seed', 9999) f.close() - np.random.seed(9999) - _preval_same = np.random.uniform() - np.random.seed(5) - _preval_diff = np.random.uniform(1000) + set_random_seed(9999) + _preval_same = get_random_uniform(1) + set_random_seed(5) + _preval_diff = get_random_uniform(1000) delta = pyDeltaRCM(input_file=p) assert delta.seed == 9999 - _postval_same = np.random.uniform() + _postval_same = get_random_uniform(1) assert _preval_same == _postval_same From 5edba66183b70ec2f4c11eee6b9e121d1a08d765 Mon Sep 17 00:00:00 2001 From: amoodie Date: Mon, 11 May 2020 21:57:50 -0500 Subject: [PATCH 09/10] addtl changes to defaults and plotting and timing --- pyDeltaRCM/default.yml | 4 ++-- pyDeltaRCM/deltaRCM_tools.py | 2 +- tests/time_water.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyDeltaRCM/default.yml b/pyDeltaRCM/default.yml index 65809d9c..b09e76be 100644 --- a/pyDeltaRCM/default.yml +++ b/pyDeltaRCM/default.yml @@ -81,10 +81,10 @@ save_depth_figs: default: False save_discharge_figs: type: 'bool' - default: True + default: False save_velocity_figs: type: 'bool' - default: True + default: False save_eta_grids: type: 'bool' default: False diff --git a/pyDeltaRCM/deltaRCM_tools.py b/pyDeltaRCM/deltaRCM_tools.py index d8f79998..8482e45e 100644 --- a/pyDeltaRCM/deltaRCM_tools.py +++ b/pyDeltaRCM/deltaRCM_tools.py @@ -187,7 +187,7 @@ def output_data(self): if self.save_eta_figs: plt.pcolor(self.eta) - plt.clim(self.clim_eta[0], self.clim_eta[1]) + # plt.clim(self.clim_eta[0], self.clim_eta[1]) plt.colorbar() plt.axis('equal') self.save_figure(self.prefix + "eta_" + str(timestep)) diff --git a/tests/time_water.py b/tests/time_water.py index ecf04ca7..4296aba9 100644 --- a/tests/time_water.py +++ b/tests/time_water.py @@ -36,4 +36,4 @@ # run the profiler for one more update() cProfile.run('delta.update()', '../temp/updatetime') p = pstats.Stats('../temp/updatetime') -p.strip_dirs().sort_stats('cumtime').print_stats() +p.strip_dirs().sort_stats('tottime').print_stats() From 1c38f66a30acfa7942ca6869ab095d958b778316 Mon Sep 17 00:00:00 2001 From: Andrew Moodie Date: Tue, 2 Jun 2020 19:31:14 -0400 Subject: [PATCH 10/10] disable jitting for coverage tests --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index b4cc7d31..15bebf8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ jobs: include: - stage: coverage python: "3.8" + before_install: + - NUMBA_DISABLE_JIT=1 install: - pip install -r requirements.txt - pip install -e .