diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index cf55b9b03b..6d43825ccd 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -36,10 +36,10 @@ jobs:
python3-dev python3-pip
shell: bash
- name: Set up Python3
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Install Python3 dependencies
@@ -48,7 +48,7 @@ jobs:
pip3 install -U pip setuptools
pip3 install --user -r requirements.txt
- name: Restore compiler cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
${{runner.workspace}}/ccache
@@ -82,7 +82,7 @@ jobs:
lcov --capture --directory . --no-external --output-file build/coverage-run.info --exclude "*/ext/*"
(cd build; lcov --add-tracefile coverage-base.info --add-tracefile coverage-run.info --output-file coverage.info)
(cd build; lcov --list coverage.info)
- - uses: codecov/codecov-action@v3
+ - uses: codecov/codecov-action@v4
with:
files: ./build/coverage.info
fail_ci_if_error: true
diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml
index e347bf4fd7..3b7bfd7693 100644
--- a/.github/workflows/formatting.yml
+++ b/.github/workflows/formatting.yml
@@ -15,7 +15,7 @@ jobs:
name: C/C++, CMake and Python
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Update submodule
working-directory: ${{runner.workspace}}/nmodl
run: git submodule update --init cmake/hpc-coding-conventions
diff --git a/.github/workflows/nmodl-ci.yml b/.github/workflows/nmodl-ci.yml
index 1a66988d20..0341cedef1 100644
--- a/.github/workflows/nmodl-ci.yml
+++ b/.github/workflows/nmodl-ci.yml
@@ -73,11 +73,11 @@ jobs:
shell: bash
- name: Set up Python3
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Install Python3 dependencies
working-directory: ${{runner.workspace}}/nmodl
@@ -141,7 +141,7 @@ jobs:
echo -----
- name: Restore compiler cache
- uses: pat-s/always-upload-cache@v3
+ uses: actions/cache@v4
with:
path: |
${{runner.workspace}}/ccache
@@ -149,6 +149,7 @@ jobs:
restore-keys: |
${{hashfiles('matrix.json')}}-${{github.ref}}-
${{hashfiles('matrix.json')}}-
+ save-always: true
- name: Build
shell: bash
@@ -183,7 +184,7 @@ jobs:
run: |
cmake --build . --target install
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: ctest-results-${{hashfiles('matrix.json')}}
path: ${{runner.workspace}}/nmodl/build/Testing/*/Test.xml
diff --git a/.github/workflows/nmodl-doc.yml b/.github/workflows/nmodl-doc.yml
index 56a8df8e86..22ab1192b3 100644
--- a/.github/workflows/nmodl-doc.yml
+++ b/.github/workflows/nmodl-doc.yml
@@ -47,11 +47,11 @@ jobs:
shell: bash
- name: Set up Python3
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -68,7 +68,7 @@ jobs:
uses: mxschmitt/action-tmate@v3
- name: Restore compiler cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
${{runner.workspace}}/ccache
diff --git a/.github/workflows/sonarsource.yml b/.github/workflows/sonarsource.yml
index 220280be8d..45b09e7457 100644
--- a/.github/workflows/sonarsource.yml
+++ b/.github/workflows/sonarsource.yml
@@ -22,10 +22,10 @@ jobs:
python3-dev python3-pip
shell: bash
- name: Set up Python3
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Install Python3 dependencies
@@ -49,6 +49,7 @@ jobs:
run: |
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/
- name: Run sonar-scanner
+ continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/INSTALL.rst b/INSTALL.rst
index 28098deab1..72fff92bde 100644
--- a/INSTALL.rst
+++ b/INSTALL.rst
@@ -224,4 +224,4 @@ You can build the entire documentation simply by using sphinx from
.. code:: sh
- python3 setup.py build_ext --inplace docs -G "Unix Makefiles"
+ python3 setup.py build_ext --inplace docs
diff --git a/docs/contents/ions.rst b/docs/contents/ions.rst
new file mode 100644
index 0000000000..898bad69e5
--- /dev/null
+++ b/docs/contents/ions.rst
@@ -0,0 +1,109 @@
+Ions
+====
+
+NEURON supports computing the ion currents and ion concentrations. For each segment
+there can be separate current for separate ions, i.e. one for sodium ions and
+another for calcium ions.
+
+There are five variables associated with ions: the current (``ina``), the
+concentration inside the segment adjacent to the membrane (``nai``), the
+concentration outside the segment adjacent to the membrane (``nao``), the
+reversal potential (``ena``) and the derivative if the current w.r.t. the
+voltage (``dinadv``). The names should be split as ``i{na}`` and therefore
+refer to the value for sodium, for calcium it would have been ``ica``.
+
+These variables are physical properties of the segment. Therefore, there exists
+one mechanism per ion. MOD files can include code to read or write these
+variables.
+
+NDMOL Keywords
+--------------
+A MOD file seeking to use ions should use ``USEION`` as follows:
+
+.. code::
+
+ NEURON {
+ USEION na READ ina WRITE ena
+ }
+
+ ASSIGNED {
+ ena (mV)
+ ina (mA / cm2)
+ }
+
+Multiple ions are expressed by one line of ``USEION`` per ion.
+
+The ``{ion_name}`` is a string giving the ion a name. For sodium it's ``na``
+and for calcium ``ca``. If the no other mechanisms have defined an ion with
+this name a new ion mechanism is created.
+
+Both ``READ`` and ``WRITE`` are optional and are followed by a comma separated
+list of ion variable names, e.g. ``ina, ena``.
+
+Keyword: WRITE
+~~~~~~~~~~~~~~
+
+Writing Ion Currents
+^^^^^^^^^^^^^^^^^^^^
+
+In MOD file one can set the value of ion variables.
+
+.. code::
+
+ BREAKPOINT {
+ ina = gna*(v - ena)
+ }
+
+Semantically, this states that the contribution of the Hodgkin-Huxley model to
+the overall sodium current in that segment is ``gna*(v - ena)``. Since
+everything is at the level of segment, we'll not keep repeating "for that
+segment". Similarly, each mechanism computes a `local` contribution, i.e. the
+contribution due to this mechanism to the actual `global` ion current.
+
+Therefore, code for the following must be generated:
+
+1. Compute the local contribution to the sodium current.
+2. Increment the total, local, current contribution by ``ina``.
+3. Increment the global sodium current by ``ina``.
+4. Compute local derivative of ``ina`` w.r.t. the voltage.
+5. Increment the global derivative of the sodium current w.r.t. the voltage.
+
+The global current must also be updated as usual. However, this isn't ion
+specific and hence omitted.
+
+
+Storage
+-------
+
+Each mechanism that specifies ``USEION na`` contains a copy of all used ion
+variables and pointers to the shared values in the ion mechanism, see Figure 1.
+
+The pointer to the variable in the ion mechanism is prefixed with ``ion_``,
+e.g. during initialization we might copy the shared value ``*ion_ena[i]`` to
+``ena[i]`` (the copy local to the mechanism using the ion).
+
+.. figure:: ../images/ion_storage.svg
+
+ Figure 1: Ion mechanism for sodium (``na``) and its use in the
+ Hodgkin-Huxley mechanism. This figure shows the NEURON memory layout.
+
+
+Optimizing Storage
+~~~~~~~~~~~~~~~~~~
+
+Since the common pattern is to only access the values of a particular instance,
+the local copy isn't needed. I might facilitate SIMD, but it could be replaces
+by local variables, see Figure 2.
+
+.. figure:: ../images/ion_storage-opt.svg
+
+ Figure 2: Optimized ion storage layout.
+
+
+This optimization is implemented in NMODL. It can be activated on the CLI via
+
+.. code:: sh
+
+ nmodl ... codegen --opt-ionvar-copy
+
+
diff --git a/docs/contents/pointers.rst b/docs/contents/pointers.rst
new file mode 100644
index 0000000000..9add4b8e32
--- /dev/null
+++ b/docs/contents/pointers.rst
@@ -0,0 +1,30 @@
+NMODL "pointers"
+================
+
+Mechanisms can refer to values in other mechanisms, e.g. the sodium current
+``ina``. Therefore, it supports a notion of "pointer", called ``Datum``. A datum
+can store a pointer to a double, a stable pointer to a double, integers, or
+pointers to anything else.
+
+Integer Variables
+-----------------
+One important subset of Datum are pointers to RANGE variables. Meaning they are
+pointers to parameters in other mechanisms or pointers to the parameters
+associated with each node, e.g. the voltage. Since the storage of RANGE
+variable is controlled by NEURON/CoreNEURON, these pointers have stronger
+semantics than a ``double*``.
+
+These make up the majority of usecases for Datum; and are considered the
+well-mannered subset.
+
+In CoreNEURON this subset of Datums are treated differently for other Datums.
+Because CoreNEURON stores the values these Datums can point to in a single
+contiguous array of doubles, the "pointers" can be expressed as indices into
+this array.
+
+Therefore, this subset of Datums is referred to as "integer variables".
+
+In NEURON these pointers are a ``data_handle`` to the value they point to.
+Before the simulation phase they are "resolved" and a cache stores a list of
+``double*`` to the appropriate values.
+
diff --git a/docs/images/ion_storage-opt.svg b/docs/images/ion_storage-opt.svg
new file mode 100644
index 0000000000..d933b4eaab
--- /dev/null
+++ b/docs/images/ion_storage-opt.svg
@@ -0,0 +1,516 @@
+
+
+
+
diff --git a/docs/images/ion_storage.svg b/docs/images/ion_storage.svg
new file mode 100644
index 0000000000..28af536e69
--- /dev/null
+++ b/docs/images/ion_storage.svg
@@ -0,0 +1,829 @@
+
+
+
+
diff --git a/docs/index.rst b/docs/index.rst
index 4a494781f7..f923c6723b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -19,6 +19,8 @@ About NMODL
:caption: Contents:
contents/visitors
+ contents/ions
+ contents/pointers
.. toctree::
:maxdepth: 3
diff --git a/src/codegen/codegen_acc_visitor.cpp b/src/codegen/codegen_acc_visitor.cpp
index 68ff009471..20624b6df7 100644
--- a/src/codegen/codegen_acc_visitor.cpp
+++ b/src/codegen/codegen_acc_visitor.cpp
@@ -62,17 +62,8 @@ void CodegenAccVisitor::print_atomic_reduction_pragma() {
void CodegenAccVisitor::print_backend_includes() {
- /**
- * Artificial cells are executed on CPU. As Random123 is allocated on GPU by default,
- * we have to disable GPU allocations using `DISABLE_OPENACC` macro.
- */
- if (info.artificial_cell) {
- printer->add_line("#undef DISABLE_OPENACC");
- printer->add_line("#define DISABLE_OPENACC");
- } else {
- printer->add_line("#include ");
- printer->add_line("#include ");
- }
+ printer->add_line("#include ");
+ printer->add_line("#include ");
}
diff --git a/src/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/codegen/codegen_coreneuron_cpp_visitor.cpp
index 08ec11a658..ed919fa887 100644
--- a/src/codegen/codegen_coreneuron_cpp_visitor.cpp
+++ b/src/codegen/codegen_coreneuron_cpp_visitor.cpp
@@ -87,140 +87,6 @@ int CodegenCoreneuronCppVisitor::position_of_int_var(const std::string& name) co
}
-/**
- * \details Current variable used in breakpoint block could be local variable.
- * In this case, neuron has already renamed the variable name by prepending
- * "_l". In our implementation, the variable could have been renamed by
- * one of the pass. And hence, we search all local variables and check if
- * the variable is renamed. Note that we have to look into the symbol table
- * of statement block and not breakpoint.
- */
-std::string CodegenCoreneuronCppVisitor::breakpoint_current(std::string current) const {
- auto breakpoint = info.breakpoint_node;
- if (breakpoint == nullptr) {
- return current;
- }
- auto symtab = breakpoint->get_statement_block()->get_symbol_table();
- auto variables = symtab->get_variables_with_properties(NmodlType::local_var);
- for (const auto& var: variables) {
- auto renamed_name = var->get_name();
- auto original_name = var->get_original_name();
- if (current == original_name) {
- current = renamed_name;
- break;
- }
- }
- return current;
-}
-
-
-/**
- * \details Depending upon the block type, we have to print read/write ion variables
- * during code generation. Depending on block/procedure being printed, this
- * method return statements as vector. As different code backends could have
- * different variable names, we rely on backend-specific read_ion_variable_name
- * and write_ion_variable_name method which will be overloaded.
- */
-std::vector CodegenCoreneuronCppVisitor::ion_read_statements(BlockType type) const {
- if (optimize_ion_variable_copies()) {
- return ion_read_statements_optimized(type);
- }
- std::vector statements;
- for (const auto& ion: info.ions) {
- auto name = ion.name;
- for (const auto& var: ion.reads) {
- auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var);
- if (iter != ion.implicit_reads.end()) {
- continue;
- }
- auto variable_names = read_ion_variable_name(var);
- auto first = get_variable_name(variable_names.first);
- auto second = get_variable_name(variable_names.second);
- statements.push_back(fmt::format("{} = {};", first, second));
- }
- for (const auto& var: ion.writes) {
- if (ion.is_ionic_conc(var)) {
- auto variables = read_ion_variable_name(var);
- auto first = get_variable_name(variables.first);
- auto second = get_variable_name(variables.second);
- statements.push_back(fmt::format("{} = {};", first, second));
- }
- }
- }
- return statements;
-}
-
-
-std::vector CodegenCoreneuronCppVisitor::ion_read_statements_optimized(
- BlockType type) const {
- std::vector statements;
- for (const auto& ion: info.ions) {
- for (const auto& var: ion.writes) {
- if (ion.is_ionic_conc(var)) {
- auto variables = read_ion_variable_name(var);
- auto first = "ionvar." + variables.first;
- const auto& second = get_variable_name(variables.second);
- statements.push_back(fmt::format("{} = {};", first, second));
- }
- }
- }
- return statements;
-}
-
-// NOLINTNEXTLINE(readability-function-cognitive-complexity)
-std::vector CodegenCoreneuronCppVisitor::ion_write_statements(BlockType type) {
- std::vector statements;
- for (const auto& ion: info.ions) {
- std::string concentration;
- auto name = ion.name;
- for (const auto& var: ion.writes) {
- auto variable_names = write_ion_variable_name(var);
- if (ion.is_ionic_current(var)) {
- if (type == BlockType::Equation) {
- auto current = breakpoint_current(var);
- auto lhs = variable_names.first;
- auto op = "+=";
- auto rhs = get_variable_name(current);
- if (info.point_process) {
- auto area = get_variable_name(naming::NODE_AREA_VARIABLE);
- rhs += fmt::format("*(1.e2/{})", area);
- }
- statements.push_back(ShadowUseStatement{lhs, op, rhs});
- }
- } else {
- if (!ion.is_rev_potential(var)) {
- concentration = var;
- }
- auto lhs = variable_names.first;
- auto op = "=";
- auto rhs = get_variable_name(variable_names.second);
- statements.push_back(ShadowUseStatement{lhs, op, rhs});
- }
- }
-
- if (type == BlockType::Initial && !concentration.empty()) {
- int index = 0;
- if (ion.is_intra_cell_conc(concentration)) {
- index = 1;
- } else if (ion.is_extra_cell_conc(concentration)) {
- index = 2;
- } else {
- /// \todo Unhandled case in neuron implementation
- throw std::logic_error(fmt::format("codegen error for {} ion", ion.name));
- }
- auto ion_type_name = fmt::format("{}_type", ion.name);
- auto lhs = fmt::format("int {}", ion_type_name);
- auto op = "=";
- auto rhs = get_variable_name(ion_type_name);
- statements.push_back(ShadowUseStatement{lhs, op, rhs});
- auto statement = conc_write_statement(ion.name, concentration, index);
- statements.push_back(ShadowUseStatement{statement, "", ""});
- }
- }
- return statements;
-}
-
-
/**
* \details Often top level verbatim blocks use variables with old names.
* Here we process if we are processing verbatim block at global scope.
@@ -256,11 +122,6 @@ std::string CodegenCoreneuronCppVisitor::process_verbatim_token(const std::strin
}
-bool CodegenCoreneuronCppVisitor::ion_variable_struct_required() const {
- return optimize_ion_variable_copies() && info.ion_has_write_variable();
-}
-
-
/**
* \details This can be override in the backend. For example, parameters can be constant
* except in INITIAL block where they are set to 0. As initial block is/can be
@@ -1189,18 +1050,6 @@ std::string CodegenCoreneuronCppVisitor::register_mechanism_arguments() const {
}
-std::pair CodegenCoreneuronCppVisitor::read_ion_variable_name(
- const std::string& name) {
- return {name, naming::ION_VARNAME_PREFIX + name};
-}
-
-
-std::pair CodegenCoreneuronCppVisitor::write_ion_variable_name(
- const std::string& name) {
- return {naming::ION_VARNAME_PREFIX + name, name};
-}
-
-
std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& ion_name,
const std::string& concentration,
int index) {
@@ -1223,28 +1072,6 @@ std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string&
}
-/**
- * If mechanisms dependency level execution is enabled then certain updates
- * like ionic current contributions needs to be atomically updated. In this
- * case we first update current mechanism's shadow vector and then add statement
- * to queue that will be used in reduction queue.
- */
-std::string CodegenCoreneuronCppVisitor::process_shadow_update_statement(
- const ShadowUseStatement& statement,
- BlockType /* type */) {
- // when there is no operator or rhs then that statement doesn't need shadow update
- if (statement.op.empty() && statement.rhs.empty()) {
- auto text = statement.lhs + ";";
- return text;
- }
-
- // return regular statement
- auto lhs = get_variable_name(statement.lhs);
- auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs);
- return text;
-}
-
-
/****************************************************************************************/
/* Code-specific printing routines for code generation */
/****************************************************************************************/
@@ -1259,6 +1086,15 @@ void CodegenCoreneuronCppVisitor::print_first_pointer_var_index_getter() {
}
+void CodegenCoreneuronCppVisitor::print_first_random_var_index_getter() {
+ printer->add_newline(2);
+ print_device_method_annotation();
+ printer->push_block("static inline int first_random_var_index()");
+ printer->fmt_line("return {};", info.first_random_var_index);
+ printer->pop_block();
+}
+
+
void CodegenCoreneuronCppVisitor::print_num_variable_getter() {
printer->add_newline(2);
print_device_method_annotation();
@@ -1435,24 +1271,6 @@ std::string CodegenCoreneuronCppVisitor::global_variable_name(const SymbolType&
}
-std::string CodegenCoreneuronCppVisitor::update_if_ion_variable_name(
- const std::string& name) const {
- std::string result(name);
- if (ion_variable_struct_required()) {
- if (info.is_ion_read_variable(name)) {
- result = naming::ION_VARNAME_PREFIX + name;
- }
- if (info.is_ion_write_variable(name)) {
- result = "ionvar." + name;
- }
- if (info.is_current(name)) {
- result = "ionvar." + name;
- }
- }
- return result;
-}
-
-
std::string CodegenCoreneuronCppVisitor::get_variable_name(const std::string& name,
bool use_instance) const {
const std::string& varname = update_if_ion_variable_name(name);
@@ -2293,6 +2111,19 @@ void CodegenCoreneuronCppVisitor::print_instance_variable_setup() {
printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)",
method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD));
cast_inst_and_assert_validity();
+
+ // delete random streams
+ if (info.random_variables.size()) {
+ printer->add_line("int pnodecount = ml->_nodecount_padded;");
+ printer->add_line("int nodecount = ml->nodecount;");
+ printer->add_line("Datum* indexes = ml->pdata;");
+ printer->push_block("for (int id = 0; id < nodecount; id++)");
+ for (const auto& var: info.random_variables) {
+ const auto& name = get_variable_name(var->get_name());
+ printer->fmt_line("nrnran123_deletestream((nrnran123_State*){});", name);
+ }
+ printer->pop_block();
+ }
print_instance_struct_delete_from_device();
printer->add_multi_line(R"CODE(
delete inst;
@@ -3561,6 +3392,7 @@ void CodegenCoreneuronCppVisitor::print_namespace_end() {
void CodegenCoreneuronCppVisitor::print_common_getters() {
print_first_pointer_var_index_getter();
+ print_first_random_var_index_getter();
print_net_receive_arg_size_getter();
print_thread_getters();
print_num_variable_getter();
diff --git a/src/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/codegen/codegen_coreneuron_cpp_visitor.hpp
index f09c5bf9c2..9c43a4cbe0 100644
--- a/src/codegen/codegen_coreneuron_cpp_visitor.hpp
+++ b/src/codegen/codegen_coreneuron_cpp_visitor.hpp
@@ -112,42 +112,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
int position_of_int_var(const std::string& name) const override;
- /**
- * Determine the variable name for the "current" used in breakpoint block taking into account
- * intermediate code transformations.
- * \param current The variable name for the current used in the model
- * \return The name for the current to be printed in C++
- */
- std::string breakpoint_current(std::string current) const;
-
-
- /**
- * For a given output block type, return statements for all read ion variables
- *
- * \param type The type of code block being generated
- * \return A \c vector of strings representing the reading of ion variables
- */
- std::vector ion_read_statements(BlockType type) const;
-
-
- /**
- * For a given output block type, return minimal statements for all read ion variables
- *
- * \param type The type of code block being generated
- * \return A \c vector of strings representing the reading of ion variables
- */
- std::vector ion_read_statements_optimized(BlockType type) const;
-
-
- /**
- * For a given output block type, return statements for writing back ion variables
- *
- * \param type The type of code block being generated
- * \return A \c vector of strings representing the write-back of ion variables
- */
- std::vector ion_write_statements(BlockType type);
-
-
/**
* Process a token in a verbatim block for possible variable renaming
* \param token The verbatim token to be processed
@@ -156,13 +120,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
std::string process_verbatim_token(const std::string& token);
- /**
- * Check if a structure for ion variables is required
- * \return \c true if a structure fot ion variables must be generated
- */
- bool ion_variable_struct_required() const;
-
-
/**
* Check if variable is qualified as constant
* \param name The name of variable
@@ -331,7 +288,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
/**
* Check if ion variable copies should be avoided
*/
- bool optimize_ion_variable_copies() const;
+ bool optimize_ion_variable_copies() const override;
/**
@@ -552,22 +509,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
std::string register_mechanism_arguments() const override;
- /**
- * Return ion variable name and corresponding ion read variable name
- * \param name The ion variable name
- * \return The ion read variable name
- */
- static std::pair read_ion_variable_name(const std::string& name);
-
-
- /**
- * Return ion variable name and corresponding ion write variable name
- * \param name The ion variable name
- * \return The ion write variable name
- */
- static std::pair write_ion_variable_name(const std::string& name);
-
-
/**
* Generate Function call statement for nrn_wrote_conc
* \param ion_name The name of the ion variable
@@ -577,21 +518,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
*/
std::string conc_write_statement(const std::string& ion_name,
const std::string& concentration,
- int index);
-
- /**
- * Process shadow update statement
- *
- * If the statement requires reduction then add it to vector of reduction statement and return
- * statement using shadow update
- *
- * \param statement The statement that might require shadow updates
- * \param type The target backend code block type
- * \return The generated target backend code
- */
- std::string process_shadow_update_statement(const ShadowUseStatement& statement,
- BlockType type);
-
+ int index) override;
/****************************************************************************************/
/* Code-specific printing routines for code generations */
@@ -605,6 +532,13 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
void print_first_pointer_var_index_getter();
+ /**
+ * Print the getter method for index position of first RANDOM variable
+ *
+ */
+ void print_first_random_var_index_getter();
+
+
/**
* Print the getter methods for float and integer variables count
*
@@ -657,14 +591,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor {
/****************************************************************************************/
- /**
- * Determine the updated name if the ion variable has been optimized
- * \param name The ion variable name
- * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena)
- */
- std::string update_if_ion_variable_name(const std::string& name) const;
-
-
/**
* Determine the name of a \c float variable given its symbol
*
diff --git a/src/codegen/codegen_cpp_visitor.cpp b/src/codegen/codegen_cpp_visitor.cpp
index 4fbf7e6fe2..e22abc221c 100644
--- a/src/codegen/codegen_cpp_visitor.cpp
+++ b/src/codegen/codegen_cpp_visitor.cpp
@@ -10,6 +10,7 @@
#include "codegen/codegen_helper_visitor.hpp"
#include "codegen/codegen_utils.hpp"
#include "visitors/rename_visitor.hpp"
+#include "visitors/visitor_utils.hpp"
namespace nmodl {
namespace codegen {
@@ -25,6 +26,10 @@ using symtab::syminfo::NmodlType;
/* Common helper routines accross codegen functions */
/****************************************************************************************/
+bool CodegenCppVisitor::ion_variable_struct_required() const {
+ return optimize_ion_variable_copies() && info.ion_has_write_variable();
+}
+
std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) {
std::string str;
@@ -145,7 +150,6 @@ bool CodegenCppVisitor::defined_method(const std::string& name) const {
return function && function->has_any_property(properties);
}
-
int CodegenCppVisitor::float_variables_size() const {
return codegen_float_variables.size();
}
@@ -209,6 +213,191 @@ bool CodegenCppVisitor::need_semicolon(const Statement& node) {
return true;
}
+/**
+ * \details Depending upon the block type, we have to print read/write ion variables
+ * during code generation. Depending on block/procedure being printed, this
+ * method return statements as vector. As different code backends could have
+ * different variable names, we rely on backend-specific read_ion_variable_name
+ * and write_ion_variable_name method which will be overloaded.
+ */
+std::vector CodegenCppVisitor::ion_read_statements(BlockType type) const {
+ if (optimize_ion_variable_copies()) {
+ return ion_read_statements_optimized(type);
+ }
+ std::vector statements;
+ for (const auto& ion: info.ions) {
+ auto name = ion.name;
+ for (const auto& var: ion.reads) {
+ auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var);
+ if (iter != ion.implicit_reads.end()) {
+ continue;
+ }
+ auto variable_names = read_ion_variable_name(var);
+ auto first = get_variable_name(variable_names.first);
+ auto second = get_variable_name(variable_names.second);
+ statements.push_back(fmt::format("{} = {};", first, second));
+ }
+ for (const auto& var: ion.writes) {
+ if (ion.is_ionic_conc(var)) {
+ auto variables = read_ion_variable_name(var);
+ auto first = get_variable_name(variables.first);
+ auto second = get_variable_name(variables.second);
+ statements.push_back(fmt::format("{} = {};", first, second));
+ }
+ }
+ }
+ return statements;
+}
+
+
+std::vector CodegenCppVisitor::ion_read_statements_optimized(BlockType type) const {
+ std::vector statements;
+ for (const auto& ion: info.ions) {
+ for (const auto& var: ion.writes) {
+ if (ion.is_ionic_conc(var)) {
+ auto variables = read_ion_variable_name(var);
+ auto first = "ionvar." + variables.first;
+ const auto& second = get_variable_name(variables.second);
+ statements.push_back(fmt::format("{} = {};", first, second));
+ }
+ }
+ }
+ return statements;
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity)
+std::vector CodegenCppVisitor::ion_write_statements(BlockType type) {
+ std::vector statements;
+ for (const auto& ion: info.ions) {
+ std::string concentration;
+ auto name = ion.name;
+ for (const auto& var: ion.writes) {
+ auto variable_names = write_ion_variable_name(var);
+ if (ion.is_ionic_current(var)) {
+ if (type == BlockType::Equation) {
+ auto current = breakpoint_current(var);
+ auto lhs = variable_names.first;
+ auto op = "+=";
+ auto rhs = get_variable_name(current);
+ if (info.point_process) {
+ auto area = get_variable_name(naming::NODE_AREA_VARIABLE);
+ rhs += fmt::format("*(1.e2/{})", area);
+ }
+ statements.push_back(ShadowUseStatement{lhs, op, rhs});
+ }
+ } else {
+ if (!ion.is_rev_potential(var)) {
+ concentration = var;
+ }
+ auto lhs = variable_names.first;
+ auto op = "=";
+ auto rhs = get_variable_name(variable_names.second);
+ statements.push_back(ShadowUseStatement{lhs, op, rhs});
+ }
+ }
+
+ if (type == BlockType::Initial && !concentration.empty()) {
+ int index = 0;
+ if (ion.is_intra_cell_conc(concentration)) {
+ index = 1;
+ } else if (ion.is_extra_cell_conc(concentration)) {
+ index = 2;
+ } else {
+ /// \todo Unhandled case in neuron implementation
+ throw std::logic_error(fmt::format("codegen error for {} ion", ion.name));
+ }
+ auto ion_type_name = fmt::format("{}_type", ion.name);
+ auto lhs = fmt::format("int {}", ion_type_name);
+ auto op = "=";
+ auto rhs = get_variable_name(ion_type_name);
+ statements.push_back(ShadowUseStatement{lhs, op, rhs});
+ auto statement = conc_write_statement(ion.name, concentration, index);
+ statements.push_back(ShadowUseStatement{statement, "", ""});
+ }
+ }
+ return statements;
+}
+
+/**
+ * If mechanisms dependency level execution is enabled then certain updates
+ * like ionic current contributions needs to be atomically updated. In this
+ * case we first update current mechanism's shadow vector and then add statement
+ * to queue that will be used in reduction queue.
+ */
+std::string CodegenCppVisitor::process_shadow_update_statement(const ShadowUseStatement& statement,
+ BlockType /* type */) {
+ // when there is no operator or rhs then that statement doesn't need shadow update
+ if (statement.op.empty() && statement.rhs.empty()) {
+ auto text = statement.lhs + ";";
+ return text;
+ }
+
+ // return regular statement
+ auto lhs = get_variable_name(statement.lhs);
+ auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs);
+ return text;
+}
+
+
+/**
+ * \details Current variable used in breakpoint block could be local variable.
+ * In this case, neuron has already renamed the variable name by prepending
+ * "_l". In our implementation, the variable could have been renamed by
+ * one of the pass. And hence, we search all local variables and check if
+ * the variable is renamed. Note that we have to look into the symbol table
+ * of statement block and not breakpoint.
+ */
+std::string CodegenCppVisitor::breakpoint_current(std::string current) const {
+ auto breakpoint = info.breakpoint_node;
+ if (breakpoint == nullptr) {
+ return current;
+ }
+ auto symtab = breakpoint->get_statement_block()->get_symbol_table();
+ auto variables = symtab->get_variables_with_properties(NmodlType::local_var);
+ for (const auto& var: variables) {
+ auto renamed_name = var->get_name();
+ auto original_name = var->get_original_name();
+ if (current == original_name) {
+ current = renamed_name;
+ break;
+ }
+ }
+ return current;
+}
+
+
+/****************************************************************************************/
+/* Routines for returning variable name */
+/****************************************************************************************/
+
+std::string CodegenCppVisitor::update_if_ion_variable_name(const std::string& name) const {
+ std::string result(name);
+ if (ion_variable_struct_required()) {
+ if (info.is_ion_read_variable(name)) {
+ result = naming::ION_VARNAME_PREFIX + name;
+ }
+ if (info.is_ion_write_variable(name)) {
+ result = "ionvar." + name;
+ }
+ if (info.is_current(name)) {
+ result = "ionvar." + name;
+ }
+ }
+ return result;
+}
+
+
+std::pair CodegenCppVisitor::read_ion_variable_name(
+ const std::string& name) {
+ return {name, naming::ION_VARNAME_PREFIX + name};
+}
+
+
+std::pair CodegenCppVisitor::write_ion_variable_name(
+ const std::string& name) {
+ return {naming::ION_VARNAME_PREFIX + name, name};
+}
+
/****************************************************************************************/
/* Main printing routines for code generation */
@@ -235,7 +424,20 @@ void CodegenCppVisitor::print_global_var_struct_decl() {
void CodegenCppVisitor::print_function_call(const FunctionCall& node) {
const auto& name = node.get_node_name();
- auto function_name = name;
+
+ // return C++ function name for RANDOM construct function
+ // e.g. nrnran123_negexp for random_negexp
+ auto get_renamed_random_function =
+ [&](const std::string& name) -> std::pair {
+ if (codegen::naming::RANDOM_FUNCTIONS_MAPPING.count(name)) {
+ return {codegen::naming::RANDOM_FUNCTIONS_MAPPING[name], true};
+ }
+ return {name, false};
+ };
+ std::string function_name;
+ bool is_random_function;
+ std::tie(function_name, is_random_function) = get_renamed_random_function(name);
+
if (defined_method(name)) {
function_name = method_name(name);
}
@@ -265,6 +467,12 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) {
}
}
+ // first argument to random functions need to be type casted
+ // from void* to nrnran123_State*.
+ if (is_random_function && !arguments.empty()) {
+ printer->add_text("(nrnran123_State*)");
+ }
+
print_vector_elements(arguments, ", ");
printer->add_text(')');
}
@@ -684,6 +892,15 @@ void CodegenCppVisitor::update_index_semantics() {
index += size;
}
+ for (auto& var: info.random_variables) {
+ if (info.first_random_var_index == -1) {
+ info.first_random_var_index = index;
+ }
+ int size = var->get_length();
+ info.semantics.emplace_back(index, naming::RANDOM_SEMANTIC, size);
+ index += size;
+ }
+
if (info.diam_used) {
info.semantics.emplace_back(index++, naming::DIAM_VARIABLE, 1);
}
@@ -863,6 +1080,12 @@ std::vector CodegenCppVisitor::get_int_variables() {
}
}
+ for (const auto& var: info.random_variables) {
+ auto name = var->get_name();
+ variables.emplace_back(make_symbol(name), true);
+ variables.back().symbol->add_properties(NmodlType::random_var);
+ }
+
if (info.diam_used) {
variables.emplace_back(make_symbol(naming::DIAM_VARIABLE));
}
diff --git a/src/codegen/codegen_cpp_visitor.hpp b/src/codegen/codegen_cpp_visitor.hpp
index 549519d396..b3d5511da3 100644
--- a/src/codegen/codegen_cpp_visitor.hpp
+++ b/src/codegen/codegen_cpp_visitor.hpp
@@ -406,6 +406,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor {
/* Common helper routines accross codegen functions */
/****************************************************************************************/
+ /**
+ * Check if a structure for ion variables is required
+ * \return \c true if a structure fot ion variables must be generated
+ */
+ bool ion_variable_struct_required() const;
+
/**
* Generate the string representing the procedure parameter declaration
@@ -590,6 +596,56 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor {
std::vector get_int_variables();
+ /**
+ * For a given output block type, return statements for all read ion variables
+ *
+ * \param type The type of code block being generated
+ * \return A \c vector of strings representing the reading of ion variables
+ */
+ std::vector ion_read_statements(BlockType type) const;
+
+
+ /**
+ * For a given output block type, return minimal statements for all read ion variables
+ *
+ * \param type The type of code block being generated
+ * \return A \c vector of strings representing the reading of ion variables
+ */
+ std::vector ion_read_statements_optimized(BlockType type) const;
+
+
+ /**
+ * For a given output block type, return statements for writing back ion variables
+ *
+ * \param type The type of code block being generated
+ * \return A \c vector of strings representing the write-back of ion variables
+ */
+ std::vector ion_write_statements(BlockType type);
+
+
+ /**
+ * Process shadow update statement
+ *
+ * If the statement requires reduction then add it to vector of reduction statement and return
+ * statement using shadow update
+ *
+ * \param statement The statement that might require shadow updates
+ * \param type The target backend code block type
+ * \return The generated target backend code
+ */
+ std::string process_shadow_update_statement(const ShadowUseStatement& statement,
+ BlockType type);
+
+
+ /**
+ * Determine the variable name for the "current" used in breakpoint block taking into account
+ * intermediate code transformations.
+ * \param current The variable name for the current used in the model
+ * \return The name for the current to be printed in C++
+ */
+ std::string breakpoint_current(std::string current) const;
+
+
/****************************************************************************************/
/* Backend specific routines */
/****************************************************************************************/
@@ -609,6 +665,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor {
*/
virtual void print_global_var_struct_decl();
+ /**
+ * Check if ion variable copies should be avoided
+ */
+ virtual bool optimize_ion_variable_copies() const = 0;
/****************************************************************************************/
/* Printing routines for code generation */
@@ -808,6 +868,16 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor {
return std::make_shared(name, ModToken());
}
+ /**
+ * Generate Function call statement for nrn_wrote_conc
+ * \param ion_name The name of the ion variable
+ * \param concentration The name of the concentration variable
+ * \param index
+ * \return The string representing the function call
+ */
+ virtual std::string conc_write_statement(const std::string& ion_name,
+ const std::string& concentration,
+ int index) = 0;
/****************************************************************************************/
/* Code-specific printing routines for code generations */
@@ -830,6 +900,13 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor {
/* Routines for returning variable name */
/****************************************************************************************/
+ /**
+ * Determine the updated name if the ion variable has been optimized
+ * \param name The ion variable name
+ * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena)
+ */
+ std::string update_if_ion_variable_name(const std::string& name) const;
+
/**
* Determine the name of a \c float variable given its symbol
@@ -887,6 +964,30 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor {
bool use_instance = true) const = 0;
+ /**
+ * Return ion variable name and corresponding ion read variable name.
+ *
+ * Example:
+ * {"ena", "ion_ena"} = read_ion_variable_name("ena");
+ *
+ * \param name The ion variable name
+ * \return The ion read variable name
+ */
+ static std::pair read_ion_variable_name(const std::string& name);
+
+
+ /**
+ * Return ion variable name and corresponding ion write variable name
+ *
+ * Example:
+ * {"ion_ena", "ena"} = write_ion_variable_name("ena");
+ *
+ * \param name The ion variable name
+ * \return The ion write variable name
+ */
+ static std::pair write_ion_variable_name(const std::string& name);
+
+
/****************************************************************************************/
/* Main printing routines for code generation */
/****************************************************************************************/
diff --git a/src/codegen/codegen_helper_visitor.cpp b/src/codegen/codegen_helper_visitor.cpp
index fa9e4438b6..72dc2508f2 100644
--- a/src/codegen/codegen_helper_visitor.cpp
+++ b/src/codegen/codegen_helper_visitor.cpp
@@ -269,6 +269,12 @@ void CodegenHelperVisitor::find_non_range_variables() {
// clang-format on
info.pointer_variables = psymtab->get_variables_with_properties(properties);
+ /// find RANDOM variables
+ // clang-format off
+ properties = NmodlType::random_var;
+ // clang-format on
+ info.random_variables = psymtab->get_variables_with_properties(properties);
+
// find special variables like diam, area
// clang-format off
properties = NmodlType::assigned_definition
diff --git a/src/codegen/codegen_info.hpp b/src/codegen/codegen_info.hpp
index c8e25e2e39..2f626e031b 100644
--- a/src/codegen/codegen_info.hpp
+++ b/src/codegen/codegen_info.hpp
@@ -12,6 +12,7 @@
* \brief Various types to store code generation specific information
*/
+#include
#include
#include
#include
@@ -20,6 +21,7 @@
#include "ast/ast.hpp"
#include "symtab/symbol_table.hpp"
+
namespace nmodl {
namespace codegen {
@@ -109,12 +111,55 @@ struct Ion {
return is_intra_cell_conc(text) || is_extra_cell_conc(text);
}
+ /// Is the variable name `text` related to this ion?
+ ///
+ /// Example: For sodium this is true for any of `"ena"`, `"ina"`, `"nai"`
+ /// and `"nao"`; but not `ion_ina`, etc.
+ bool is_ionic_variable(const std::string& text) const {
+ return is_ionic_conc(text) || is_ionic_current(text) || is_rev_potential(text);
+ }
+
+ bool is_current_derivative(const std::string& text) const {
+ return text == ("di" + name + "dv");
+ }
+
/// for a given ion, return different variable names/properties
/// like internal/external concentration, reversial potential,
/// ionic current etc.
static std::vector get_possible_variables(const std::string& ion_name) {
return {"i" + ion_name, ion_name + "i", ion_name + "o", "e" + ion_name};
}
+
+ /// Variable index in the ion mechanism.
+ ///
+ /// For sodium (na), the `var_name` must be one of `ina`, `ena`, `nai`,
+ /// `nao` or `dinadv`. Replace `na` with the analogous for other ions.
+ ///
+ /// In NRN the order is:
+ /// 0: ena
+ /// 1: nai
+ /// 2: nao
+ /// 3: ina
+ /// 4: dinadv
+ int variable_index(const std::string& var_name) const {
+ if (is_rev_potential(var_name)) {
+ return 0;
+ }
+ if (is_intra_cell_conc(var_name)) {
+ return 1;
+ }
+ if (is_extra_cell_conc(var_name)) {
+ return 2;
+ }
+ if (is_ionic_current(var_name)) {
+ return 3;
+ }
+ if (is_current_derivative(var_name)) {
+ return 4;
+ }
+
+ throw std::runtime_error(fmt::format("Invalid `var_name == {}`.", var_name));
+ }
};
@@ -327,9 +372,15 @@ struct CodegenInfo {
/// pointer or bbcore pointer variables
std::vector pointer_variables;
+ /// RANDOM variables
+ std::vector random_variables;
+
/// index/offset for first pointer variable if exist
int first_pointer_var_index = -1;
+ /// index/offset for first RANDOM variable if exist
+ int first_random_var_index = -1;
+
/// tqitem index in integer variables
/// note that if tqitem doesn't exist then the default value should be 0
int tqitem_index = 0;
diff --git a/src/codegen/codegen_naming.hpp b/src/codegen/codegen_naming.hpp
index e8240b7df2..78f2b25ead 100644
--- a/src/codegen/codegen_naming.hpp
+++ b/src/codegen/codegen_naming.hpp
@@ -7,8 +7,8 @@
#pragma once
-#include