def _generate_cpp_code(n, p, use_normal, name): # C++ implementation # Inversion transform sampling if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) loc = n * p scale = np.sqrt(n * p * (1 - p)) cpp_code = ''' float %NAME%(const int vectorisation_idx) { return _randn(vectorisation_idx) * %SCALE% + %LOC%; } ''' cpp_code = replace(cpp_code, { '%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': name }) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cpp_code = ''' long %NAME%(const int vectorisation_idx) { long X = 0; double px = %QN%; double U = _rand(vectorisation_idx); while (U > px) { X++; if (X > %BOUND%) { X = 0; px = %QN%; U = _rand(vectorisation_idx); } else { U -= px; px = ((%N%-X+1) * %P% * px)/(X*%Q%); } } return %RETURN_VALUE%; } ''' cpp_code = replace( cpp_code, { '%N%': '%d' % n, '%P%': '%.15f' % P, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': name }) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} return {'support_code': cpp_code}, dependencies
def _generate_cpp_code(n, p, use_normal, name): # C++ implementation # Inversion transform sampling if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) loc = n*p scale = np.sqrt(n*p*(1-p)) cpp_code = ''' float %NAME%(const int vectorisation_idx) { return _randn(vectorisation_idx) * %SCALE% + %LOC%; } ''' cpp_code = replace(cpp_code, {'%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': name}) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cpp_code = ''' long %NAME%(const int vectorisation_idx) { long X = 0; double px = %QN%; double U = _rand(vectorisation_idx); while (U > px) { X++; if (X > %BOUND%) { X = 0; px = %QN%; U = _rand(vectorisation_idx); } else { U -= px; px = ((%N%-X+1) * %P% * px)/(X*%Q%); } } return %RETURN_VALUE%; } ''' cpp_code = replace(cpp_code, {'%N%': '%d' % n, '%P%': '%.15f' % P, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': name}) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} return {'support_code': cpp_code}, dependencies
def _generate_cython_code(n, p, use_normal, name): # Cython implementation # Inversion transform sampling if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) cython_code = """ cdef float %NAME%(const int vectorisation_idx): return _randn(vectorisation_idx) * %SCALE% + %LOC% """ cython_code = replace(cython_code, { '%SCALE%': f'{scale:.15f}', '%LOC%': f'{loc:.15f}', '%NAME%': name }) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cython_code = """ cdef long %NAME%(const int vectorisation_idx): cdef long X = 0 cdef double px = %QN% cdef double U = _rand(vectorisation_idx) while U > px: X += 1 if X > %BOUND%: X = 0 px = %QN% U = _rand(vectorisation_idx) else: U -= px px = ((%N%-X+1) * %P% * px)/(X*%Q%) return %RETURN_VALUE% """ cython_code = replace( cython_code, { '%N%': f'{int(n)}', '%P%': f'{p:.15f}', '%Q%': f'{q:.15f}', '%QN%': f'{qn:.15f}', '%BOUND%': f'{bound:.15f}', '%RETURN_VALUE%': f'{int(n)}-X' if reverse else 'X', '%NAME%': name }) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} return cython_code, dependencies
def create_cython_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) code = ''' cdef double %NAME%(const double t, const int i): global _namespace%NAME%_values cdef double epsilon = %DT% / %K% if i < 0 or i >= %COLS%: return _numpy.nan cdef int timestep = (int)((t/epsilon + 0.5)/%K%) if timestep < 0: timestep = 0 elif timestep >= %ROWS%: timestep = %ROWS%-1 return _namespace%NAME%_values[timestep*%COLS% + i] ''' code = replace( code, { '%NAME%': self.name, '%DT%': '%.18f' % dt, '%K%': str(K), '%COLS%': str(self.values.shape[1]), '%ROWS%': str(self.values.shape[0]) }) return code
def create_cpp_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) support_code = ''' inline double %NAME%(const double t, const int i) { const double epsilon = %DT% / %K%; if (i < 0 || i >= %COLS%) return NAN; int timestep = (int)((t/epsilon + 0.5)/%K%); if(timestep < 0) timestep = 0; else if(timestep >= %ROWS%) timestep = %ROWS%-1; return _namespace%NAME%_values[timestep*%COLS% + i]; } ''' support_code = replace(support_code, {'%NAME%': self.name, '%DT%': '%.18f' % dt, '%K%': str(K), '%COLS%': str(self.values.shape[1]), '%ROWS%': str(self.values.shape[0])}) cpp_code = {'support_code': support_code} return cpp_code
def create_cython_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) code = """ cdef double %NAME%(const double t, const int i): global _namespace%NAME%_values cdef double epsilon = %DT% / %K% if i < 0 or i >= %COLS%: return _numpy.nan cdef int timestep = (int)(((t-%OFFSET%)/epsilon + 0.5)/%K%) if timestep < 0: timestep = 0 elif timestep >= %ROWS%: timestep = %ROWS%-1 return _namespace%NAME%_values[timestep*%COLS% + i] """ code = replace( code, { "%NAME%": self.name, "%DT%": "%.18f" % dt, "%K%": str(K), "%COLS%": str(self.values.shape[1]), "%ROWS%": str(self.values.shape[0]), "%OFFSET%": str(np.asarray(tOffset)), }, ) return code
def create_cpp_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) support_code = """ inline double %NAME%(const double t, const int i) { const double epsilon = %DT% / %K%; if (i < 0 || i >= %COLS%) return NAN; int timestep = (int)(((t-%OFFSET%)/epsilon + 0.5)/%K%); if(timestep < 0) timestep = 0; else if(timestep >= %ROWS%) timestep = %ROWS%-1; return _namespace%NAME%_values[timestep*%COLS% + i]; } """ support_code = replace( support_code, { "%NAME%": self.name, "%DT%": "%.18f" % dt, "%K%": str(K), "%COLS%": str(self.values.shape[1]), "%ROWS%": str(self.values.shape[0]), "%OFFSET%": str(np.asarray(tOffset)), }, ) cpp_code = {"support_code": support_code} return cpp_code
def cuda_impl(owner): K = _find_K(owner.clock.dt_, dt) code = ''' __host__ __device__ static inline double %NAME%(const double t, const int i) { const double epsilon = %DT% / %K%; if (i < 0 || i >= %COLS%) return NAN; int timestep = (int)((t/epsilon + 0.5)/%K%); if(timestep < 0) timestep = 0; else if(timestep >= %ROWS%) timestep = %ROWS%-1; return _namespace%NAME%_values[timestep*%COLS% + i]; } ''' code = replace( code, { '%NAME%': name, '%DT%': f'{dt:.18f}', '%K%': str(K), '%COLS%': str(values.shape[1]), '%ROWS%': str(values.shape[0]) }) return code
def create_cpp_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) support_code = ''' static inline double %NAME%(const double t, const int i) { const double epsilon = %DT% / %K%; if (i < 0 || i >= %COLS%) return NAN; int timestep = (int)((t/epsilon + 0.5)/%K%); if(timestep < 0) timestep = 0; else if(timestep >= %ROWS%) timestep = %ROWS%-1; return _namespace%NAME%_values[timestep*%COLS% + i]; } ''' support_code = replace( support_code, { '%NAME%': self.name, '%DT%': '%.18f' % dt, '%K%': str(K), '%COLS%': str(self.values.shape[1]), '%ROWS%': str(self.values.shape[0]) }) cpp_code = {'support_code': support_code} return cpp_code
def _generate_cython_code(n, p, use_normal, name): # Cython implementation # Inversion transform sampling if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) cython_code = ''' cdef float %NAME%(const int vectorisation_idx): return _randn(vectorisation_idx) * %SCALE% + %LOC% ''' cython_code = replace(cython_code, {'%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': name}) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cython_code = ''' cdef long %NAME%(const int vectorisation_idx): cdef long X = 0 cdef double px = %QN% cdef double U = _rand(vectorisation_idx) while U > px: X += 1 if X > %BOUND%: X = 0 px = %QN% U = _rand(vectorisation_idx) else: U -= px px = ((%N%-X+1) * %P% * px)/(X*%Q%) return %RETURN_VALUE% ''' cython_code = replace(cython_code, {'%N%': '%d' % n, '%P%': '%.15f' % p, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': name}) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} return cython_code, dependencies
def cython_impl(owner): K = _find_K(owner.clock.dt_, dt) code = """ cdef double %NAME%(const double t, const int i): global _namespace%NAME%_values cdef double epsilon = %DT% / %K% if i < 0 or i >= %COLS%: return _numpy.nan cdef int timestep = (int)((t/epsilon + 0.5)/%K%) if timestep < 0: timestep = 0 elif timestep >= %ROWS%: timestep = %ROWS%-1 return _namespace%NAME%_values[timestep*%COLS% + i] """ code = replace(code, {'%NAME%': name, '%DT%': f'{dt:.18f}', '%K%': str(K), '%COLS%': str(values.shape[1]), '%ROWS%': str(values.shape[0])}) return code
def generate_rand_code(rand_func, owner): nb_threads = prefs.devices.cpp_standalone.openmp_threads if nb_threads == 0: # no OpenMP thread_number = '0' else: thread_number = 'omp_get_thread_num()' if rand_func == 'rand': rk_call = 'rk_double' elif rand_func == 'randn': rk_call = 'rk_gauss' else: raise AssertionError(rand_func) code = ''' double _%RAND_FUNC%(const int _vectorisation_idx) { return %RK_CALL%(brian::_mersenne_twister_states[%THREAD_NUMBER%]); } ''' code = replace(code, {'%THREAD_NUMBER%': thread_number, '%RAND_FUNC%': rand_func, '%RK_CALL%': rk_call}) return {'support_code': code}
def generate_rand_code(rand_func, owner): nb_threads = prefs.devices.cpp_standalone.openmp_threads if nb_threads == 0: # no OpenMP thread_number = '0' else: thread_number = 'omp_get_thread_num()' if rand_func == 'rand': rk_call = 'rk_double' elif rand_func == 'randn': rk_call = 'rk_gauss' else: raise AssertionError(rand_func) code = """ double _%RAND_FUNC%(const int _vectorisation_idx) { return %RK_CALL%(brian::_mersenne_twister_states[%THREAD_NUMBER%]); } """ code = replace(code, {'%THREAD_NUMBER%': thread_number, '%RAND_FUNC%': rand_func, '%RK_CALL%': rk_call}) return {'support_code': code}
def create_cython_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) code = ''' cdef double %NAME%(const double t, const int i): global _namespace%NAME%_values cdef double epsilon = %DT% / %K%; if i < 0 or i >= %COLS%: return _numpy.nan cdef int timestep = (int)((t/epsilon + 0.5)/%K%) if timestep < 0: timestep = 0 elif timestep >= %ROWS%: timestep = %ROWS%-1 return _namespace%NAME%_values[timestep*%COLS% + i] ''' code = replace(code, {'%NAME%': self.name, '%DT%': '%.18f' % dt, '%K%': str(K), '%COLS%': str(self.values.shape[1]), '%ROWS%': str(self.values.shape[0])}) return code
def __init__(self, n, p, approximate=True, name='_binomial*'): Nameable.__init__(self, name) #Python implementation use_normal = approximate and (n * p > 5) and n * (1 - p) > 5 if use_normal: loc = n * p scale = np.sqrt(n * p * (1 - p)) def sample_function(vectorisation_idx): try: N = len(vectorisation_idx) except TypeError: N = int(vectorisation_idx) return np.random.normal(loc, scale, size=N) else: def sample_function(vectorisation_idx): try: N = len(vectorisation_idx) except TypeError: N = int(vectorisation_idx) return np.random.binomial(n, p, size=N) Function.__init__(self, pyfunc=lambda: sample_function(1), arg_units=[], return_unit=1, stateless=False) self.implementations.add_implementation('numpy', sample_function) # Common pre-calculations for C++ and Cython if use_normal: loc = n * p scale = np.sqrt(n * p * (1 - p)) else: reverse = p > 0.5 if reverse: P = 1.0 - p else: P = p q = 1.0 - P qn = np.exp(n * np.log(q)) bound = min(n, n * P + 10.0 * np.sqrt(n * P * q + 1)) # C++ implementation # Inversion transform sampling if use_normal: loc = n * p scale = np.sqrt(n * p * (1 - p)) cpp_code = ''' float %NAME%(const int vectorisation_idx) { return _randn(vectorisation_idx) * %SCALE% + %LOC%; } ''' cpp_code = replace( cpp_code, { '%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': self.name }) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cpp_code = ''' long %NAME%(const int vectorisation_idx) { long X = 0; double px = %QN%; double U = _rand(vectorisation_idx); while (U > px) { X++; if (X > %BOUND%) { X = 0; px = %QN%; U = _rand(vectorisation_idx); } else { U -= px; px = ((%N%-X+1) * %P% * px)/(X*%Q%); } } return %RETURN_VALUE%; } ''' cpp_code = replace( cpp_code, { '%N%': '%d' % n, '%P%': '%.15f' % P, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': self.name }) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} self.implementations.add_implementation('cpp', {'support_code': cpp_code}, dependencies=dependencies, name=self.name) # Cython implementation # Inversion transform sampling if use_normal: cython_code = ''' cdef float %NAME%(const int vectorisation_idx): return _randn(vectorisation_idx) * %SCALE% + %LOC% ''' cython_code = replace( cython_code, { '%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': self.name }) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cython_code = ''' cdef long %NAME%(const int vectorisation_idx): cdef long X = 0 cdef double px = %QN% cdef double U = _rand(vectorisation_idx) while U > px: X += 1 if X > %BOUND%: X = 0 px = %QN% U = _rand(vectorisation_idx) else: U -= px px = ((%N%-X+1) * %P% * px)/(X*%Q%) return %RETURN_VALUE% ''' cython_code = replace( cython_code, { '%N%': '%d' % n, '%P%': '%.15f' % p, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': self.name }) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} self.implementations.add_implementation('cython', cython_code, dependencies=dependencies, name=self.name)
def _generate_cuda_code(n, p, use_normal, name): # CUDA implementation # Inversion transform sampling if not '_binomial' in name: # TODO: mark issue raise NameError("Currently the `name` parameter of `BinomialFunction` " "needs to have '_binomial' in it, got " "'{}'".format(name)) float_suffix = '' float_dtype = 'float' if prefs['core.default_float_dtype'] == np.float64: float_suffix = '_double' float_dtype = 'double' # TODO: we should load the state once in scalar code and pass as function # argument, needs modification of CUDAGenerator._add_user_function to # modify scalar code if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) cuda_code = ''' __host__ __device__ %DTYPE% %NAME%(const int vectorisation_idx) { #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0)) return curand_normal%SUFFIX%(brian::d_curand_states + vectorisation_idx) * %SCALE% + %LOC%; #else return host_randn(vectorisation_idx) * %SCALE% + %LOC%; #endif } ''' cuda_code = replace( cuda_code, { '%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': name, '%DTYPE%': float_dtype, '%SUFFIX%': float_suffix }) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cuda_code = ''' __host__ __device__ long %NAME%(const int vectorisation_idx) { #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0)) curandState localState = brian::d_curand_states[vectorisation_idx]; %DTYPE% U = curand_uniform%SUFFIX%(&localState); #else %DTYPE% U = host_rand(vectorisation_idx); #endif long X = 0; %DTYPE% px = %QN%; while (U > px) { X++; if (X > %BOUND%) { X = 0; px = %QN%; #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0)) U = curand_uniform%SUFFIX%(&localState); #else U = host_rand(vectorisation_idx); #endif } else { U -= px; px = ((%N%-X+1) * %P% * px)/(X*%Q%); } } #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0)) // copy the locally changed CuRAND state back to global memory brian::d_curand_states[vectorisation_idx] = localState; #endif return %RETURN_VALUE%; } ''' cuda_code = replace( cuda_code, { '%N%': '%d' % n, '%P%': '%.15f' % P, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': name, '%DTYPE%': float_dtype, '%SUFFIX%': float_suffix }) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} return {'support_code': cuda_code}, dependencies
def __init__(self, n, p, approximate=True, name='_binomial*'): Nameable.__init__(self, name) #Python implementation use_normal = approximate and (n*p > 5) and n*(1-p) > 5 if use_normal: loc = n*p scale = np.sqrt(n*p*(1-p)) def sample_function(vectorisation_idx): try: N = len(vectorisation_idx) except TypeError: N = int(vectorisation_idx) return np.random.normal(loc, scale, size=N) else: def sample_function(vectorisation_idx): try: N = len(vectorisation_idx) except TypeError: N = int(vectorisation_idx) return np.random.binomial(n, p, size=N) Function.__init__(self, pyfunc=lambda: sample_function(1), arg_units=[], return_unit=1, stateless=False) self.implementations.add_implementation('numpy', sample_function) # Common pre-calculations for C++ and Cython if use_normal: loc = n*p scale = np.sqrt(n*p*(1-p)) else: reverse = p > 0.5 if reverse: P = 1.0 - p else: P = p q = 1.0 - P qn = np.exp(n * np.log(q)) bound = min(n, n*P + 10.0*np.sqrt(n*P*q + 1)) # C++ implementation # Inversion transform sampling if use_normal: loc = n*p scale = np.sqrt(n*p*(1-p)) cpp_code = ''' float %NAME%(const int vectorisation_idx) { return _randn(vectorisation_idx) * %SCALE% + %LOC%; } ''' cpp_code = replace(cpp_code, {'%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': self.name}) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cpp_code = ''' long %NAME%(const int vectorisation_idx) { long X = 0; double px = %QN%; double U = _rand(vectorisation_idx); while (U > px) { X++; if (X > %BOUND%) { X = 0; px = %QN%; U = _rand(vectorisation_idx); } else { U -= px; px = ((%N%-X+1) * %P% * px)/(X*%Q%); } } return %RETURN_VALUE%; } ''' cpp_code = replace(cpp_code, {'%N%': '%d' % n, '%P%': '%.15f' % P, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': self.name}) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} self.implementations.add_implementation('cpp', {'support_code': cpp_code}, dependencies=dependencies, name=self.name) # Cython implementation # Inversion transform sampling if use_normal: cython_code = ''' cdef float %NAME%(const int vectorisation_idx): return _randn(vectorisation_idx) * %SCALE% + %LOC% ''' cython_code = replace(cython_code, {'%SCALE%': '%.15f' % scale, '%LOC%': '%.15f' % loc, '%NAME%': self.name}) dependencies = {'_randn': DEFAULT_FUNCTIONS['randn']} else: # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cython_code = ''' cdef long %NAME%(const int vectorisation_idx): cdef long X = 0 cdef double px = %QN% cdef double U = _rand(vectorisation_idx) while U > px: X += 1 if X > %BOUND%: X = 0 px = %QN% U = _rand(vectorisation_idx) else: U -= px px = ((%N%-X+1) * %P% * px)/(X*%Q%) return %RETURN_VALUE% ''' cython_code = replace(cython_code, {'%N%': '%d' % n, '%P%': '%.15f' % p, '%Q%': '%.15f' % q, '%QN%': '%.15f' % qn, '%BOUND%': '%.15f' % bound, '%RETURN_VALUE%': '%d-X' % n if reverse else 'X', '%NAME%': self.name}) dependencies = {'_rand': DEFAULT_FUNCTIONS['rand']} self.implementations.add_implementation('cython', cython_code, dependencies=dependencies, name=self.name)