User-defined functions

@mstimberg Thanks. This is really an interesting workaround. Before I elaborate concerning β€œoption 1,” – thanks for trusting my programming skills – but for now, I am still on the end-user side :slight_smile:

I am experimenting with your suggestion. However, two issues seem to emerge, plus a third one that actually falls in a side question.


First: device.get_array_name
Looking into the device.get_array_name docs, the method requires a global variable to retrieve the actual array name from. What if I am actually nesting my cell definition inside a method that returns cell as an argument of a class? What is the actual global variable? Global wherein? Python or CPP? And global concerning what scope? Global the whole script scope? Or global to a parent method? The following is the hierarchy I am dealing with:

def cell_compartment(N,pars):
    ...
    compartment = NeuronGroup(N,...,events={'transitioned': 'transition', 'no_transition': 'not transition'}))
    code_upon_transition = """
                           rval = rand()
                           p_O = q_DP/Q
                           X_O = X_O + (rval<=X_O*p_O)*1 - (rval>X_O*p_O)*1
                           fraction = 1 - tau/(Q*dt/second)
                           dummy = nMGA(dt,Q,p_O,i)  <----- Referring to this line also in the **Second point**
                           """
    code_upon_notransition = """
                             tau -= Q*dt/second
                             """
    compartment.run_on_event('transitioned',code_upon_transition)
    compartment.run_on_event('notransition',code_upon_notransition)
    return compartment

def compartment_geometry(N,...):
    comps = cell_compartment(N,...)
    ... 
    return Network([comps,...])

class ComplexCell(object):
    def __init__(self, N,...):
        # Pre-defs of standalone
        set_device('cpp_standalone', directory=code_dir, build_on_run=False)
        device.delete(force=True)  # Clean codegen directory
        self._code_dir = code_dir
        self.__build = False

        # Generate model
        self.celltree = ComplexCell(N,...)

    def simulate(self,duration=1*second,build=True):
        prefs.devices.cpp_standalone.openmp_threads = 10
        prefs.logging.file_log = False
        prefs.logging.delete_log_on_exit = True
        defaultclock.dt = 0.01*msecond
        ## Run simulation
        self.celltree.run(duration, report='text')

        if build:
            # Build at the end
            device.build(directory=self._code_dir,clean=clean)
            self.__build = True

Hence if my CPP code is now:

@implementation('cpp', '''
double nMGA(double dt,double Q,double p_O,int index) {
    while(true) {
        %tau_NAME%[index] = -log(_rand(index));
        if (%tau_NAME%[index]>=%fraction_NAME%[index]*Q*dt_){
            %tau_NAME%[index] -= %fraction_NAME%[index]*Q*dt_;
        break;
        }
        else {
            %rval_NAME%[index] = _rand(index);
            %X_0_NAME%[index] += int(%rval_NAME%[index]<=%X_0_NAME%[index]*p_O) - int(%rval_NAME%[index]>%X_0_NAME%[index]*p_O);
            %fraction_NAME%[index] -= %tau_NAME%[index]/(Q*dt_);
        }
    }
    return 1.0;
}
'''.replace('%tau_NAME%', device.get_array_name(<???>.variables['tau'])).replace('%fraction_NAME%',\
            device.get_array_name(<???>.variables['fraction'])).replace('%rval_NAME%',\
            device.get_array_name(<???>.variables['rval'])).replace('%X_0_NAME%',\
            device.get_array_name(<???>.variables['X_0'])),
dependencies={'_rand': DEFAULT_FUNCTIONS['rand']})
@check_units(dt=second, Q=1, p_O=1, index='integer', result=1)
def nMGA(dt, Q, p_O, index):
   raise NotImplementedError('use standalone mode')

if __name__=='__main__':
    astro = ComplexCell(10,...)
    astro.simulate(duration=5*second)

What is my cell global variable (replaced by <???>)?


Second: index error

I am not sure. But my understanding from your explanation is that β€˜i’ is a reserved symbol in the Brian scope or within the code and always refers to the index of elements in the NeuronGroup.

Then when I attempt to compile the above, I get and error when the parser gets to the the line where I am supposed to call the nMGA function in the code_upon_transition. Here is the full error:

Traceback (most recent call last):
File "/home/maurizio/local/brian2/brian2/core/network.py", line 897, in before_run
obj.before_run(run_namespace)
File "/home/maurizio/local/brian2/brian2/groups/group.py", line 1143, in before_run
self.create_code_objects(run_namespace)
File "/home/maurizio/local/brian2/brian2/groups/group.py", line 1136, in create_code_objects
code_object = self.create_default_code_object(run_namespace)
File "/home/maurizio/local/brian2/brian2/groups/group.py", line 1118, in create_default_code_object
self.codeobj = create_runner_codeobj(group=self.group,
File "/home/maurizio/local/brian2/brian2/codegen/codeobject.py", line 396, in create_runner_codeobj
check_units_statements(c, variables)
File "/home/maurizio/local/brian2/brian2/equations/unitcheck.py", line 99, in check_units_statements
expr_unit = parse_expression_dimensions(expr, variables)
File "/home/maurizio/local/brian2/brian2/parsing/expressions.py", line 315, in parse_expression_dimensions
arg_idx = func._arg_names.index(expected_unit)
AttributeError: 'NoneType' object has no attribute 'index'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/home/maurizio/Ongoing.Projects/FSU.Project/code/astrocyte_model_class.py", line 574, in <module>
cell.simulate(duration=5*second)
File "/home/maurizio/Ongoing.Projects/FSU.Project/code/astrocyte_model_class.py", line 554, in simulate
self.celltree.run(duration, report='text')
File "/home/maurizio/local/brian2/brian2/core/base.py", line 276, in device_override_decorated_function
return getattr(curdev, name)(*args, **kwds)
File "/home/maurizio/local/brian2/brian2/devices/cpp_standalone/device.py", line 1375, in network_run
net.before_run(namespace)
File "/home/maurizio/local/brian2/brian2/core/base.py", line 278, in device_override_decorated_function
return func(*args, **kwds)
File "/home/maurizio/local/brian2/brian2/core/network.py", line 899, in before_run
raise BrianObjectException("An error occurred when preparing an object.", obj) from ex
brian2.core.base.BrianObjectException: Error encountered with object named "comp_resetter".
Object was created here (most recent call only, full details in debug log):
File "/home/maurizio/Ongoing.Projects/FSU.Project/code/astrocyte_model_class.py", line 406, in AstroCompartment
astro_compartment.run_on_event('transitioned',code_upon_transition) 

So for some reason, it seems that the index is NoneType – could that be because right not my N in terms of comps instantiation is 1 – which would imply treatment of scalar NumPy arrays differently from actual arrays (i.e., not atleast_1d for scalar arrays).


Third: Replacing internal variables by unitless versions

Looking at your suggestion, I recall that Brian offers this option of retrieving the unitless variables by appending the underscore _ character to the variable name. However, if this allegedly works in the CPP code, when I replaced dt/second by dt_, for example, in my above code_upon_transition I get the unidentified identifier error. So it seems that Brian does not like the underscored version of its internal variables. Is that right? I am a bit confused.