NEURON
codegen_helper.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <catch2/catch_test_macros.hpp>
9 
10 #include "ast/program.hpp"
12 #include "codegen/codegen_info.hpp"
13 #include "parser/nmodl_driver.hpp"
19 
20 using namespace nmodl;
21 using namespace visitor;
22 using namespace codegen;
23 
25 
26 //=============================================================================
27 // Helper for codegen related visitor
28 //=============================================================================
29 std::string run_codegen_helper_visitor(const std::string& text) {
31  const auto& ast = driver.parse_string(text);
32 
33  /// construct symbol table and run codegen helper visitor
35  CodegenHelperVisitor v;
36 
37  /// symbols/variables are collected in info object
38  const auto& info = v.analyze(*ast);
39 
40  /// semicolon separated list of variables
41  std::string variables;
42 
43  /// range variables in order of code generation
44  for (const auto& var: info.range_parameter_vars) {
45  variables += var->get_name() + ";";
46  }
47  for (const auto& var: info.range_assigned_vars) {
48  variables += var->get_name() + ";";
49  }
50  for (const auto& var: info.range_state_vars) {
51  variables += var->get_name() + ";";
52  }
53  for (const auto& var: info.assigned_vars) {
54  variables += var->get_name() + ";";
55  }
56 
57  return variables;
58 }
59 
60 CodegenInfo run_codegen_helper_get_info(const std::string& text) {
61  const auto& ast = NmodlDriver().parse_string(text);
62  /// construct symbol table and run codegen helper visitor
70  SymtabVisitor{true}.visit_program(*ast);
71 
72  bool enable_cvode = true;
73  CodegenHelperVisitor v(enable_cvode);
74  const auto info = v.analyze(*ast);
75 
76  return info;
77 }
78 
79 SCENARIO("unusual / failing mod files", "[codegen][var_order]") {
80  GIVEN("cal_mig.mod : USEION variables declared as RANGE") {
81  std::string nmodl_text = R"(
82  PARAMETER {
83  gcalbar=.003 (mho/cm2)
84  ki=.001 (mM)
85  cai = 50.e-6 (mM)
86  cao = 2 (mM)
87  q10 = 5
88  USEGHK=1
89  }
90  NEURON {
91  SUFFIX cal
92  USEION ca READ cai,cao WRITE ica
93  RANGE gcalbar, cai, ica, gcal, ggk
94  RANGE minf, tau
95  GLOBAL USEGHK
96  }
97  STATE {
98  m
99  }
100  ASSIGNED {
101  ica (mA/cm2)
102  gcal (mho/cm2)
103  minf
104  tau (ms)
105  ggk
106  }
107  DERIVATIVE state {
108  rate(v)
109  m' = (minf - m)/tau
110  }
111  )";
112 
113  THEN("ionic current variable declared as RANGE appears first") {
114  std::string expected = "gcalbar;ica;gcal;minf;tau;ggk;m;cai;cao;";
116  REQUIRE(result == expected);
117  }
118  }
119 
120  GIVEN("CaDynamics_E2.mod : USEION variables declared as STATE variable") {
121  std::string nmodl_text = R"(
122  NEURON {
123  SUFFIX CaDynamics_E2
124  USEION ca READ ica WRITE cai
125  RANGE decay, gamma, minCai, depth
126  }
127 
128  PARAMETER {
129  gamma = 0.05 : percent of free calcium (not buffered)
130  decay = 80 (ms) : rate of removal of calcium
131  depth = 0.1 (um) : depth of shell
132  minCai = 1e-4 (mM)
133  }
134 
135  ASSIGNED {ica (mA/cm2)}
136 
137  STATE {
138  cai (mM)
139  }
140 
141  DERIVATIVE states {
142  cai' = -(10000)*(ica*gamma/(2*FARADAY*depth)) - (cai - minCai)/decay
143  }
144  )";
145 
146  THEN("ion state variable is ordered after parameter and assigned ionic current") {
147  std::string expected = "gamma;decay;depth;minCai;ica;cai;";
149  REQUIRE(result == expected);
150  }
151  }
152 
153  GIVEN("cadyn.mod : same USEION variables used for read as well as write") {
154  std::string nmodl_text = R"(
155  NEURON {
156  SUFFIX cadyn
157  USEION ca READ cai,ica WRITE cai
158  RANGE ca
159  GLOBAL depth,cainf,taur
160  }
161 
162  PARAMETER {
163  depth = .1 (um)
164  taur = 200 (ms) : rate of calcium removal
165  cainf = 50e-6(mM) :changed oct2
166  cai (mM)
167  }
168 
169  ASSIGNED {
170  ica (mA/cm2)
171  drive_channel (mM/ms)
172  }
173 
174  STATE {
175  ca (mM)
176  }
177 
178  BREAKPOINT {
179  SOLVE state METHOD euler
180  }
181 
182  DERIVATIVE state {
183  ca' = drive_channel/18 + (cainf -ca)/taur*11
184  cai = ca
185  }
186  )";
187 
188  THEN("ion variables are ordered correctly") {
189  std::string expected = "ca;cai;ica;drive_channel;";
191  REQUIRE(result == expected);
192  }
193  }
194 }
195 
196 SCENARIO("Check global variable setup", "[codegen][global_variables]") {
197  GIVEN("SH_na8st.mod: modfile from reduced_dentate model") {
198  std::string const nmodl_text{R"(
199  NEURON {
200  SUFFIX na8st
201  }
202  STATE { c1 c2 }
203  BREAKPOINT {
204  SOLVE kin METHOD derivimplicit
205  }
206  INITIAL {
207  SOLVE kin STEADYSTATE derivimplicit
208  }
209  KINETIC kin {
210  ~ c1 <-> c2 (a1, b1)
211  }
212  )"};
214  const auto ast = driver.parse_string(nmodl_text);
215 
216  /// construct symbol table and run codegen helper visitor
224  SymtabVisitor{true}.visit_program(*ast);
225 
226  CodegenHelperVisitor v;
227  const auto info = v.analyze(*ast);
228  // See https://github.com/BlueBrain/nmodl/issues/736
229  THEN("Checking that primes_size and prime_variables_by_order have the expected size") {
230  REQUIRE(info.primes_size == 2);
231  REQUIRE(info.prime_variables_by_order.size() == 2);
232  }
233  }
234 }
235 
236 CodegenInfo make_codegen_info(const std::string& text) {
238  const auto& ast = driver.parse_string(text);
239 
241  CodegenHelperVisitor v;
242 
243  return v.analyze(*ast);
244 }
245 
246 TEST_CASE("Check ion write/read checks") {
247  std::string input_nmodl = R"(
248  NEURON {
249  SUFFIX test
250  USEION ca READ cai WRITE cai, eca
251  USEION na WRITE nao, ena
252  USEION K READ Ki, eK
253  RANGE x
254  }
255  ASSIGNED {
256  x
257  cai
258  eca
259  nai
260  nao
261  ena
262  Ki
263  }
264  INITIAL {
265  x = cai
266  cai = 42.0
267  x = nao
268  Ki = 42.0
269  }
270  BREAKPOINT {
271  eca = 42.0
272  x = ena
273  eK = 42.0
274  }
275  )";
276 
277  auto info = make_codegen_info(input_nmodl);
278 
279  for (const auto& ion: info.ions) {
280  if (ion.name == "ca") {
281  REQUIRE(ion.is_conc_read());
282  REQUIRE(ion.is_interior_conc_read());
283  REQUIRE(!ion.is_exterior_conc_read());
284  REQUIRE(!ion.is_rev_read());
285 
286  REQUIRE(ion.is_conc_written());
287  REQUIRE(ion.is_interior_conc_written());
288  REQUIRE(!ion.is_exterior_conc_written());
289  REQUIRE(ion.is_rev_written());
290  }
291  if (ion.name == "na") {
292  REQUIRE(!ion.is_conc_read());
293  REQUIRE(!ion.is_interior_conc_read());
294  REQUIRE(!ion.is_exterior_conc_read());
295  REQUIRE(!ion.is_rev_read());
296 
297  REQUIRE(ion.is_conc_written());
298  REQUIRE(!ion.is_interior_conc_written());
299  REQUIRE(ion.is_exterior_conc_written());
300  REQUIRE(ion.is_rev_written());
301  }
302  if (ion.name == "K") {
303  REQUIRE(ion.is_conc_read());
304  REQUIRE(ion.is_interior_conc_read());
305  REQUIRE(!ion.is_exterior_conc_read());
306  REQUIRE(ion.is_rev_read());
307 
308  REQUIRE(!ion.is_conc_written());
309  REQUIRE(!ion.is_interior_conc_written());
310  REQUIRE(!ion.is_exterior_conc_written());
311  REQUIRE(!ion.is_rev_written());
312  }
313  }
314 }
315 
316 SCENARIO("CVODE codegen") {
317  GIVEN("a mod file with a single KINETIC block") {
318  std::string input_nmodl = R"(
319  STATE {
320  x
321  }
322  KINETIC states {
323  ~ x << (a*c/3.2)
324  }
325  BREAKPOINT {
326  SOLVE states METHOD cnexp
327  })";
328 
329  const auto& info = run_codegen_helper_get_info(input_nmodl);
330  THEN("Emit CVODE") {
331  REQUIRE(info.emit_cvode);
332  }
333  }
334  GIVEN("a mod file with a single DERIVATIVE block") {
335  std::string input_nmodl = R"(
336  STATE {
337  m
338  }
339  BREAKPOINT {
340  SOLVE state METHOD derivimplicit
341  }
342  DERIVATIVE state {
343  m' = 2 * m
344  }
345  )";
346  const auto& info = run_codegen_helper_get_info(input_nmodl);
347 
348  THEN("Emit CVODE") {
349  REQUIRE(info.emit_cvode);
350  }
351  }
352  GIVEN("a mod file with a single PROCEDURE block solved with method `after_cvode`") {
353  std::string input_nmodl = R"(
354  BREAKPOINT {
355  SOLVE state METHOD after_cvode
356  }
357  PROCEDURE state() {}
358  )";
359 
360  const auto& info = run_codegen_helper_get_info(input_nmodl);
361 
362  THEN("Emit CVODE") {
363  REQUIRE(info.emit_cvode);
364  }
365  }
366  GIVEN("a mod file with a single PROCEDURE block NOT solved with method `after_cvode`") {
367  std::string input_nmodl = R"(
368  BREAKPOINT {
369  SOLVE state METHOD cnexp
370  }
371  PROCEDURE state() {}
372  )";
373 
374  const auto& info = run_codegen_helper_get_info(input_nmodl);
375 
376  THEN("Do not emit CVODE") {
377  REQUIRE(!info.emit_cvode);
378  }
379  }
380  GIVEN("a mod file with a DERIVATIVE and a KINETIC block") {
381  std::string input_nmodl = R"(
382  STATE {
383  m
384  x
385  }
386  BREAKPOINT {
387  SOLVE der METHOD derivimplicit
388  SOLVE kin METHOD cnexp
389  }
390  DERIVATIVE der {
391  m' = 2 * m
392  }
393  KINETIC kin {
394  ~ x << (a*c/3.2)
395  }
396  )";
397 
398  const auto& info = run_codegen_helper_get_info(input_nmodl);
399 
400  THEN("Do not emit CVODE") {
401  REQUIRE(!info.emit_cvode);
402  }
403  }
404  GIVEN("a mod file with a PROCEDURE and a DERIVATIVE block") {
405  std::string input_nmodl = R"(
406  STATE {
407  m
408  }
409  BREAKPOINT {
410  SOLVE der METHOD derivimplicit
411  SOLVE func METHOD cnexp
412  }
413  DERIVATIVE der {
414  m' = 2 * m
415  }
416  PROCEDURE func() {
417  }
418  )";
419 
420  const auto& info = run_codegen_helper_get_info(input_nmodl);
421 
422  THEN("Do not emit CVODE") {
423  REQUIRE(!info.emit_cvode);
424  }
425  }
426 }
Class that binds all pieces together for parsing nmodl file.
std::shared_ptr< ast::Program > parse_string(const std::string &input)
parser nmodl provided as string (used for testing)
Visitor for kinetic block statements
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor that solves ODEs using old solvers of NEURON
void visit_program(ast::Program &node) override
visit node of type ast::Program
Replace solve block statements with actual solution node in the AST.
void visit_program(ast::Program &node) override
visit node of type ast::Program
Visitor for STEADYSTATE solve statements
void visit_program(ast::Program &node) override
visit node of type ast::Program
Concrete visitor for constructing symbol table from AST.
void visit_program(ast::Program &node) override
visit node of type ast::Program
CodegenInfo make_codegen_info(const std::string &text)
TEST_CASE("Check ion write/read checks")
SCENARIO("unusual / failing mod files", "[codegen][var_order]")
CodegenInfo run_codegen_helper_get_info(const std::string &text)
std::string run_codegen_helper_visitor(const std::string &text)
Helper visitor to gather AST information to help code generation.
Various types to store code generation specific information.
#define v
Definition: md1redef.h:11
int nmodl_text
Definition: modl.cpp:58
bool parse_string(const std::string &input)
parser Units provided as string (used for testing)
Definition: unit_driver.cpp:40
double var(InputIterator begin, InputIterator end)
Definition: ivocvect.h:108
Visitor for kinetic block statements
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
static List * info
Visitor that solves ODEs using old solvers of NEURON
#define text
Definition: plot.cpp:60
Auto generated AST classes declaration.
Replace solve block statements with actual solution node in the AST.
Visitor for STEADYSTATE solve statements
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
nmodl::parser::UnitDriver driver
Definition: parser.cpp:28