User Guide
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
34  SymtabVisitor().visit_program(*ast);
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
63  SymtabVisitor{}.visit_program(*ast);
64  KineticBlockVisitor{}.visit_program(*ast);
65  SymtabVisitor{}.visit_program(*ast);
66  SteadystateVisitor{}.visit_program(*ast);
67  SymtabVisitor{}.visit_program(*ast);
68  NeuronSolveVisitor{}.visit_program(*ast);
69  SolveBlockVisitor{}.visit_program(*ast);
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;";
115  auto result = run_codegen_helper_visitor(nmodl_text);
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;";
148  auto result = run_codegen_helper_visitor(nmodl_text);
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;";
190  auto result = run_codegen_helper_visitor(nmodl_text);
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
217  SymtabVisitor{}.visit_program(*ast);
218  KineticBlockVisitor{}.visit_program(*ast);
219  SymtabVisitor{}.visit_program(*ast);
220  SteadystateVisitor{}.visit_program(*ast);
221  SymtabVisitor{}.visit_program(*ast);
222  NeuronSolveVisitor{}.visit_program(*ast);
223  SolveBlockVisitor{}.visit_program(*ast);
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 
240  SymtabVisitor().visit_program(*ast);
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 }
nmodl::parser::NmodlDriver
Class that binds all pieces together for parsing nmodl file.
Definition: nmodl_driver.hpp:67
make_codegen_info
CodegenInfo make_codegen_info(const std::string &text)
Definition: codegen_helper.cpp:222
solve_block_visitor.hpp
Replace solve block statements with actual solution node in the AST.
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
SCENARIO
SCENARIO("unusual / failing mod files", "[codegen][var_order]")
Definition: codegen_helper.cpp:79
codegen_helper_visitor.hpp
Helper visitor to gather AST information to help code generation.
kinetic_block_visitor.hpp
Visitor for kinetic block statements
steadystate_visitor.hpp
Visitor for STEADYSTATE solve statements
program.hpp
Auto generated AST classes declaration.
driver
nmodl::parser::UnitDriver driver
Definition: parser.cpp:28
nmodl::parser::UnitDriver::parse_string
bool parse_string(const std::string &input)
parser Units provided as string (used for testing)
Definition: unit_driver.cpp:40
codegen_info.hpp
Various types to store code generation specific information.
run_codegen_helper_visitor
std::string run_codegen_helper_visitor(const std::string &text)
Definition: codegen_helper.cpp:29
run_codegen_helper_get_info
CodegenInfo run_codegen_helper_get_info(const std::string &text)
Definition: codegen_helper.cpp:60
nmodl::parser::NmodlDriver::parse_string
std::shared_ptr< ast::Program > parse_string(const std::string &input)
parser nmodl provided as string (used for testing)
Definition: nmodl_driver.cpp:89
neuron_solve_visitor.hpp
Visitor that solves ODEs using old solvers of NEURON
nmodl_driver.hpp
TEST_CASE
TEST_CASE("Check ion write/read checks")
Definition: codegen_helper.cpp:232
symtab_visitor.hpp
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.