Skip to content

Make your own components

This document will describe how to create your own components in xolotl. Currently, you can create your own conductances, mechanisms and synapses.

Creating new conductances

Create a new conductance using the conductance class

Demo

A worked example showing how the conductance class can be used to generate new channels exists in ../xolotl/examples/demo_conductance.

First, create a conductance object.

newCond = xolotl.conductance;

Remember that you can always see all the properties of the object using properties(newCond).

Then, the activation steady-state function m_inf, the inactivation steady-state function h_inf, the activation time constant function tau_m, and the inactivation time constant function tau_h must be defined, as function handles.

Instantaneous Kinetics

If your conductance has instantaneous kinetics, you can set tau_m to @(V, Ca) 0. xolotl will automatically use a different integration scheme that updates m to its instantaneous steady-state value.

For example,

newCond.m_inf = @(V,Ca) 1.0 / (1.0 + exp((V-20.0)/5.0));
newCond.h_inf = @(V,Ca) 1;
newCond.tau_m = @(V,Ca) 0;
newCond.tau_h = @(V,Ca) 0;

You must also set whether this is a Calcium conductance.

newCond.is_Ca = false;

Specify the exponents of the activation p and (de)inactivation q gating variables in the current equation.

newCond.p = 4;
newCond.q = 0;

Finally, you must set the default reversal potential.

newCond.default_E = -80;

You can also set the default activation and inactivation gating variable values with newCond.default_m and newCond.default_h.

We're done! Now we can automatically generate a C++ file:

newCond.generateCPPFile('condName')

where 'condName' is what you want to name the conductance. A C++ header file will be generated at ../xolotl/c++/conductances/custom/condName.hpp. You can now use this conductance like any other, e.g. x.comp.add('custom/condName', 'gbar', 10).

Creating a new conductance by hand

It is reasonably straightforward to create your own conductance by hand (i.e., to create the C++ file yourself). A good starting point is typically to look at the C++ files for existing conductances to get a sense of what the class looks like.

This is what a skeleton of a new conductance file would look like:

#ifndef NEWCOND
#define NEWCOND

//inherit conductance class spec
class NewCond: public conductance {

public:

    // specify parameters + initial conditions
    NewCond(double gbar_, double E_, double m_, double h_)
    {
        gbar = gbar_;
        E = E_;
        m = m_;
        h = h_;

        // defaults
        if (isnan(gbar)) { gbar = 0; }
        if (isnan (E)) { E = 30; }

        // specify exponents of m and h
        p = 3;
        q = 1;

        // allow this channel to be approximated?
        AllowMInfApproximation = true; // or false if not
        AllowHInfApproximation = true;

        name = "NewCond";

        // define permeabilities to different ions
        perm.K = .1;
        perm.Na = .9; 
    }

    double m_inf(double, double);
    double h_inf(double, double);
    double tau_m(double, double);
    double tau_h(double, double);

};


double NewCond::m_inf(double V, double Ca) {return ...;}
double NewCond::h_inf(double V, double Ca) {return ...;}
double NewCond::tau_m(double V, double Ca) {return ...;}
double NewCond::tau_h(double V, double Ca) {return ...;}


#endif

xolotl.conductance" vs. "conductance

Note that there are two different classes called "conductance". One of them is an abstract C++ class that all object of type conductance must inherit from. The other is a MATLAB class that is used to generate conductances automatically. When we're talking about C++ code, we're referring to the abstract C++ class.

Why aren't there any integration routines?

Because models for conductances are so stereotyped, integration routines for them are specified in the parent conductance class. If we look at the code for the conductance class, we see this:

    virtual void integrate(double, double);
    virtual void integrateMS(int, double, double);

In C++, declaring a method "virtual" means that it will be used if there isn't anything else that overrides it. If you specify your own integrate method, then that will be used, not the built-in one.

Creating new mechanisms

Because mechanisms can be just about anything, the only way to make new mechanisms is to write new C++ files. As with conductances, new mechanisms inherit from the abstract C++ class called "mechanism" and it is typically straightforward to write a new mechanism.

Here's what a skeleton for a new mechanism would look like:

#ifndef NEWMECH
#define NEWMECH
#include <limits>


//inherit controller class spec
class NewMech: public mechanism {

protected:
public:

    // parameters
    double A = 0;
    double B = 1;

    // constructor
    NewMech(double A_, double B_) {
        A = A_;
        B = B_;
        controlling_class = "unset";
        name = "NewMech";
        mechanism_type = "foo";
    }

    void integrate(void);
    void integrateMS(int, double, double);

    double NewMechCustomMethod(double);

    // these methods allow reading out of the mechanism
    // state
    int getFullState(double * mech_state, int idx);
    double getState(int);

};


// this does nothing since our state size is 0
// otherwise we should increment idx, and
// fill in values in the mech_state array
int NewMech::getFullState(double *mech_state, int idx) {
    return idx;
}



// specify how we integrate it with the default
// solver order (a single-step integration)
void NewMech::integrate(void) {
    // insert integration routine here
}

// this method can hold anything, and you can have
// as many custom methods as you want
double NewMech::NewMechCustomMethod(double X_) {
    // insert your code here
}


#endif

Creating new synapses

Creating new synapses is similar to the process of creating new conductances by hand. New synapses inherit from the abstract C++ class synapse.

Here is a skeleton for a new synapse class that you can fill out for yourself.

// here is an example of a synapse named NewSynapse
// we define the header file and include the synapse class
#ifndef NEWSYNAPSE
#define NEWSYNAPSE

// define the class for the specific synapse here
class NewSynapse: public synapse {

public:


    // here is the constructor function
    // it accepts the maximal conductance and a single state variable
    NewSynapse(double gbar_, double s_)
    {
        gmax = g_;
        s = s_;

        // we could make it accept the reversal potential too
        // but here we have set it within the function definition
        E = -80.0;

        // set up defaults in case g_ and s_ aren't specified in MATLAB during construction
        if (isnan (s)) { s = 0; }
        if (isnan (gmax)) { gmax = 0; }
        is_electrical = false;

        name = "NewSynapse";
    }

    // all of these functions are needed for all synapses
    void integrate(void);
    void integrateMS(int, double, double);
    void checkSolvers(int);

    int getFullStateSize(void);
    void connect(compartment *pcomp1_, compartment *pcomp2_);
    int getFullState(double*, int);

    // these functions are specific to this synapse
    double s_inf(double);
    double tau_s(double);
    double sdot(double, double);

};

// this function returns the state size of the synapse
// which should be the number of state variables
// from this synapse plus one (for the synaptic current)
int NewSynapse::getFullStateSize() {
    return 2;
}

// define the steady-state gating function for this synapse
double NewSynapse::s_inf(double V_pre) {
    return 1.0/(1.0+exp((Vth - V_pre)/Delta));
}


double NewSynapse::tau_s(double sinf_) {
    // this could hold some arbitrary function
}


double NewSynapse::sdot(double V_pre, double s_) {
    // define your own function here
}


void NewSynapse::integrate(void) {
    // define your integration routing here

}


// determine which solver to use
// 0 should always be supported
void NewSynapse::checkSolvers(int k){
    if (k == 0) {
        return;
    }
    mexErrMsgTxt("[NewSynapse] Unsupported solver order\n");
}

// return the state variable, and the current
int NewSynapse::getFullState(double *syn_state, int idx) {
    // give it the current synapse variable
    syn_state[idx] = s;

    idx++;

    // the synaptic current in the post-synaptic compartment
    // is also computed here
    syn_state[idx] = gmax*s*(post_syn->V - E);
    idx++;
    return idx;
}

// the connect function defines how to wire
// two compartments together
void NewSynapse::connect(compartment *pcomp1_, compartment *pcomp2_) {
    pre_syn = pcomp1_;
    post_syn = pcomp2_;

    // tell the post-synaptic cell that we're connecting to it
    post_syn->addSynapse(this);
}

#endif

Where should I put them?

All conductances (and any other network component) are defined by an .hpp header file. You can save your new conductance anywhere on your MATLAB path. cpplab will find them, link them, and use them automatically. If cpplab can't find them, then running xolotl.rebuildCache should fix it.

How are built-in C++ files organized?

We organize our conductance files in the xolotl directory under xolotl/c++/conductances/. Within that directory, conductances are grouped by the surname of the first author on the paper from where they were sourced. For example, conductances from Liu et al. 1998 can be found in xolotl/c++/conductances/liu/.

If an author, such as Farzan Nadim or Cristina Soto-Trevino, happens to have written more than one paper from which we have extracted models, the last two digits of the paper publishing year are appended to the author name (e.g. ../nadim98). If there are multiple papers in a single year, lowercase alphabetical indices are used (e.g. ../golowasch01a).

If a paper, such as Soplata et al. 2017 describes multiple channels of a single type in different cell types (e.g. thalamocortical relay and thalamic reticular cells), then full-word descriptions can be used, such as (../soplata/thalamocortical).