Markdown exporter¶
This is the developer documentation for mdexport
package, that
provides information about understanding baseexport
’s standard dictionary,
standard markdown expander MdExpander
and writing custom expand functions.
Standard dictionary¶
The package baseexport
collects all the required Brian model information and
arranges them in an intuitive way, that can potentially be used for various
exporters and use cases. Therefore, understanding the representation will be helpful for
further manipulation.
The dictionary contains a list of run
dictionaries with each containing
information about the particular run simulation.
# list of run dictionaries
[
. . . .
{ # run dictionary
duration: <Quantity>,
components: {
. . .
},
initializers_connectors: [. . .],
inactive: [ . . .]
},
. . . .
]
Typically, a run
dictionary has four fields,
duration
: simulation lengthcomponents
: dictionary of network components likeNeuronGroup
,Synapses
, etc.initializers_connectors
: list of initializers and synaptic connectionsinactive
: list of components that were inactive for the particular run
All the Brian Network
components that are under components
field, have
components like, NeuronGroup
, Synapses
, etc and would look like,
{
'neurongroup': [. . . .],
'poissongroup': [. . . .],
'spikegeneratorgroup': [. . . .],
'statemonitor': [. . . .],
'synapses': [. . . .],
. . . .
}
Each component field has a list of objects of that component defined in the run time.
The dictionary representation of NeuronGroup
and its similar types would look like,
neurongroup: [
{
'name': <name of the group>,
'N': <population size>,
'user_method': <integration method>,
'equations': <model equations> {
'<variable name>':{ 'unit': <unit>,
'type': <equation type>
'var_type': <variable dtype>
'expr': <expression>,
'flags': <list of flags>
}
. . .
}
'events': <events> {
'<event_name>':{'threshold':{'code': <threshold code>,
'when': <when slot>,
'order': <order slot>,
'dt': <clock dt>
},
'reset':{'code': <reset code>,
'when': <when slot>,
'order': <order slot>,
'dt': <clock dt>
},
'refractory': <refractory period>
}
. . .
}
'run_regularly': <run_regularly statements>
[
{
'name': <name of run_regularly>
'code': <statement>
'dt': <run_regularly clock dt>
'when': <when slot of run_regularly>
'order': <order slot of run_regularly>
}
. . .
]
'when': <when slot of group>,
'order': <order slot of group>,
'identifiers': {'<name>': <value>,
. . .
}
}
]
Similarly, StateMonitor
and its similar types are represented like,
statemonitor: [
{
'name': <name of the group>,
'source': <name of source>,
'variables': <list of monitored variables>,
'record': <list of monitored members>,
'dt': <time step>
'when': <when slot of group>,
'order': <order slot of group>,
}
. . .
]
As Synapses
has many similarity with NeuronGroup
, the dictionary of the same
also looks similar to it, however some of the Synapses
specific fields are,
synapses: [
{
'name': <name of the synapses object>,
'equations': <model equations> {
'<variable name>':{ 'unit': <unit>,
'type': <equation type>
'var_type': <variable dtype>
'expr': <expression>,
'flags': <list of flags>
}
. . .
}
'summed_variables': <summed variables>
[
{
'target': <name of target group>,
'code': <variable name>,
'name': <name of the summed variable>,
'dt': <time step>,
'when': <when slot of run_regularly>,
'order': <order slot of run_regularly>
}
. . .
]
'pathways': <synaptic pathways>
[
{
'prepost': <pre or post event>,
'event': <event name>,
'code': <variable name>,
'source': <source group name>,
'name': <name of the summed variable>,
'clock': <time step>,
'when': <when slot of run_regularly>,
'order': <order slot of run_regularly>,
}
. . .
]
}
]
Also, the identifiers
takes into account of TimedArray
and
custom user functions.
The initializers_connectors
field contains a list of initializers and synaptic connections,
and their structure would look like,
[
{ <initializer>
'source': <source group name>,
'variable': <variable that is initialized>,
'index': <indices that are affected>,
'value': <value>, 'type': 'initializer'
},
. . .
{ <connection>
{'i': <i>, 'j': <j>,
'probability': <probability of connection>,
'n_connections': <number of connections>,
'synapses': <name of the synapse>,
'source': <source group name>,
'target': <target group name>, 'type': 'connect'
}
. . .
]
As a working example, to get the standard dictionary of model description when using STDP example,
[{'components':
{'neurongroup': [{'N': 1,
'equations': {'ge': {'expr': '-ge / taue',
'type': 'differential equation',
'unit': radian,
'var_type': 'float'},
'v': {'expr': '(ge * (Ee-v) + El - v) / taum',
'type': 'differential equation',
'unit': volt,
'var_type': 'float'}},
'events': {'spike': {'reset': {'code': 'v = vr',
'dt': 100. * usecond,
'order': 0,
'when': 'resets'},
'threshold': {'code': 'v>vt',
'dt': 100. * usecond,
'order': 0,
'when': 'thresholds'}}},
'identifiers': {'Ee': 0. * volt,
'El': -74. * mvolt,
'taue': 5. * msecond,
'taum': 10. * msecond,
'vr': -60. * mvolt,
'vt': -54. * mvolt},
'name': 'neurongroup',
'order': 0,
'user_method': 'euler',
'when': 'groups'}],
'poissongroup': [{'N': 1000,
'name': 'poissongroup',
'rates': 15. * hertz}],
'spikemonitor': [{'dt': 100. * usecond,
'event': 'spike',
'name': 'spikemonitor',
'order': 1,
'record': True,
'source': 'poissongroup',
'variables': ['i', 't'],
'when': 'thresholds'}],
'statemonitor': [{'dt': 100. * usecond,
'n_indices': 2,
'name': 'statemonitor',
'order': 0,
'record': array([0, 1], dtype=int32),
'source': 'synapses',
'variables': ['w'],
'when': 'start'}],
'synapses': [{'equations': {'Apost': {'expr': '-Apost / taupost',
'flags': ['event-driven'],
'type': 'differential equation',
'unit': radian,
'var_type': 'float'},
'Apre': {'expr': '-Apre / taupre',
'flags': ['event-driven'],
'type': 'differential equation',
'unit': radian,
'var_type': 'float'},
'w': {'type': 'parameter',
'unit': radian,
'var_type': 'float'}},
'identifiers': {'dApost': -0.000105,
'dApre': 0.0001,
'gmax': 0.01,
'taupost': 20. * msecond,
'taupre': 20. * msecond},
'name': 'synapses',
'pathways': [{'clock': 100. * usecond,
'code': 'ge += w\n'
'Apre += dApre\n'
'w = clip(w + Apost, 0, gmax)',
'event': 'spike',
'name': 'synapses_pre',
'order': -1,
'prepost': 'pre',
'source': 'poissongroup',
'target': 'neurongroup',
'when': 'synapses'},
{'clock': 100. * usecond,
'code': 'Apost += dApost\n'
'w = clip(w + Apre, 0, gmax)',
'event': 'spike',
'name': 'synapses_post',
'order': 1,
'prepost': 'post',
'source': 'neurongroup',
'target': 'poissongroup',
'when': 'synapses'}],
'source': 'poissongroup',
'target': 'neurongroup'}]},
'duration': 100. * second,
'initializers_connectors': [{'index': True,
'source': 'poissongroup',
'type': 'initializer',
'value': 15. * hertz,
'variable': 'rates'},
{'n_connections': 1,
'probability': 1,
'source': 'poissongroup',
'synapses': 'synapses',
'target': 'neurongroup',
'type': 'connect'},
{'identifiers': {'gmax': 0.01},
'index': 'True',
'source': 'synapses',
'type': 'initializer',
'value': 'rand() * gmax',
'variable': 'w'}]}]
MdExpander¶
To use the dictionary representation for creating markdown strings, by
default MdExpander
class is used.
The class contains expand functions for different Brian components,
such that the user can easily override the particular function without
affecting others. Also, different options can be given during the instantiation of the object
and pass to the set_device
or device.build()
.
As a simple example, to use GitHub based markdown rendering for mathematical statements, and use Brian specific jargons,
from brian2tools import MdExpander # import the standard expander
# custom expander
custom = MdExpander(github_md=True, brian_verbose=True)
set_device('markdown', expander=custom) # pass the custom expander
Details about the monitors are not included by default in the output markdown and to include them,
# custom expander to include monitors
custom_with_monitors = MdExpander(include_monitors=True)
set_device('markdown', expander=custom_with_monitors)
Also, the order of variable initializations and connect
statements
are not shown in the markdown output by default, this may likely result to inaccurate results, when the
values of variables during synaptic connections are contingent upon their order. In that case, the order
shall be included to markdown output as,
# custom expander to include monitors
custom = MdExpander(keep_initializer_order=True)
set_device('markdown', expander=custom)
The modified output with details about the order of initialization and Synaptic connection, when running on the Working example would look like,
Network details
Neuron population :
Group neurongroup, consisting of 1 neurons.
Model dynamics:
=
=
The equations are integrated with the 'euler' method.
Events:
If , a spike event is triggered and ←.
Constants: = , = , = , = , = , and =
Poisson spike source :
- Name poissongroup, with population size 1000 and rate as .
Synapse :
Connections synapses, connecting poissongroup to neurongroup.
Model dynamics:
Parameter (dimensionless)
=
=
For each pre-synaptic spike: Increase by , Increase by , ←
For each post-synaptic spike: Increase by , ←
Constants: = , = , = , = , and =
Initializing at start and Synaptic connection :
Variable of poissongroup initialized with
Connection from poissongroup to neurongroup. Pairwise connections.
Variable of synapses initialized with , where = .
The simulation was run for 100. s
Similarly, author
and add_meta
options can also be customized during object instantiation, to
add author name and meta data respectively in the header of the markdown output.
Typically, expand function of the component would follow the structure similar to,
def expand_object(self, object_dict):
# use object_dict information to write md_string
md_string = . . . object_dict['field_A']
return md_string
However, enumerating components like identifiers
, pathways
have two functions in which the first
one simply loops the list and the second one expands the member. For example, with identifiers
,
def expand_identifiers(self, identifiers_list):
# calls `expand_identifier` iteratively
markdown_str = ''
for identifier in identifiers_list:
. . .
markdown_str += self.expand_identifier(identifier)
return markdown_str
def expand_identifier(self, identifier):
# individual identifier expander
markdown_str = ''
. . . # use identifier dict to write markdown strings
return markdown_str
All the individual expand functions are tied to create_md_string
function that calls and collects
all the returned markdown strings to pass it to device.md_text
Writing custom expand class¶
With the understanding of standard dictionary representation and default markdown expand class (MdExpander
),
writing custom expand class becomes very straightforward. As a working example, the custom expander
class to write equations in a table like format,
from brian2tools import MdExpander
from markdown_strings import table # import table from markdown_strings
# custom expander class to do custom modifications for model equations
class Dynamics_table(MdExpander):
def expand_equation(self, var, equation):
# if differential equation pass `differential` flag as `True` to
# render_expression()
if equation['type'] == 'differential equation':
return (self.render_expression(var, differential=True) +
'=' + self.render_expression(equation['expr']))
else:
return (self.render_expression(var) +
'=' + self.render_expression(equation['expr']))
def expand_equations(self, equations):
diff_rend_eqn = ['Differential equations']
sub_rend_eqn = ['Sub-Expressions']
# loop over
for (var, eqn) in equations.items():
if eqn['type'] == 'differential equation':
diff_rend_eqn.append(self.expand_equation(var, eqn))
if eqn['type'] == 'subexpression':
sub_rend_eqn.append(self.expand_equation(var, eqn))
# now pad space for shorter one
if len(diff_rend_eqn) > len(sub_rend_eqn):
shorter = diff_rend_eqn
longer = sub_rend_eqn
else:
shorter = sub_rend_eqn
longer = diff_rend_eqn
for _ in range(len(longer) - len(shorter)):
shorter.append('')
# return table of rendered equations
return table([shorter, longer])
custom = Dynamics_table()
set_device('markdown', expander=custom) # pass the custom expander object
when using the above custom class with COBAHH example, the equation part would look like,
Dynamics:
Sub-Expressions | Differential equations |
---|---|
= | = |
= | = |
= | = |
= | = |
= | = |
= | = |