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
).