User Guide
units.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 "utils/fmt.h"
9 #include <catch2/catch_test_macros.hpp>
10 
11 #include "ast/double.hpp"
12 #include "ast/factor_def.hpp"
13 #include "ast/program.hpp"
14 #include "parser/nmodl_driver.hpp"
15 #include "src/config/config.h"
20 
21 using namespace nmodl;
22 using namespace visitor;
23 using namespace test;
24 using namespace test_utils;
25 
27 
28 //=============================================================================
29 // Unit visitor tests
30 //=============================================================================
31 
32 std::tuple<std::shared_ptr<ast::Program>, std::shared_ptr<units::UnitTable>> run_units_visitor(
33  const std::string& text) {
35  driver.parse_string(text);
36  const auto& ast = driver.get_ast();
37 
38  // Parse nrnunits.lib file and the UNITS block of the mod file
39  const std::string units_lib_path(NrnUnitsLib::get_path());
40  UnitsVisitor units_visitor = UnitsVisitor(units_lib_path);
41 
42  units_visitor.visit_program(*ast);
43 
44  // Keep the UnitTable created from parsing unit file and UNITS
45  // block of the mod file
46  parser::UnitDriver units_driver = units_visitor.get_unit_driver();
47  std::shared_ptr<units::UnitTable> unit_table = units_driver.table;
48 
49  // check that, after visitor rearrangement, parents are still up-to-date
50  CheckParentVisitor().check_ast(*ast);
51 
52  return {ast, unit_table};
53 }
54 
55 
56 /**
57  * @brief Returns all the \c UnitDef s of the \c ast::Program
58  *
59  * Visit AST to find all the ast::UnitDef nodes to print their
60  * unit names, factors and dimensions as they are calculated in
61  * the units::UnitTable
62  * The \c UnitDef s are printed in the format:
63  * \code
64  * <unit_name> <unit_value>: <dimensions>
65  * \endcode
66  *
67  * If the unit is constant then instead of the dimensions we print \c constant
68  *
69  * @arg ast \c ast::Program to look for \c UnitDef s
70  * @arg unit_table \c units::UnitTable to look for the definitions of the units
71  *
72  * @return std::string Unit definitions
73  */
74 std::string get_unit_definitions(const ast::Program& ast, const units::UnitTable& unit_table) {
75  std::stringstream ss;
76  const auto& unit_defs = collect_nodes(ast, {ast::AstNodeType::UNIT_DEF});
77 
78  for (const auto& unit_def: unit_defs) {
79  auto unit_name = unit_def->get_node_name();
80  unit_name.erase(remove_if(unit_name.begin(), unit_name.end(), isspace), unit_name.end());
81  auto unit = unit_table.get_unit(unit_name);
82  ss << fmt::format("{} {:g}:", unit_name, unit->get_factor());
83  // Dimensions of the unit are printed to check that the units are successfully
84  // parsed to the units::UnitTable
85  int dimension_id = 0;
86  auto constant = true;
87  for (const auto& dimension: unit->get_dimensions()) {
88  if (dimension != 0) {
89  constant = false;
90  ss << ' ' << unit_table.get_base_unit_name(dimension_id) << dimension;
91  }
92  dimension_id++;
93  }
94  if (constant) {
95  ss << " constant";
96  }
97  ss << '\n';
98  }
99  return ss.str();
100 }
101 
102 /**
103  * @brief Returns all the \c FactorDef s of the \c ast::Program
104  *
105  * Visit AST to find all the ast::FactorDef nodes to print their
106  * unit names and factors as they are calculated to be printed
107  * to the generated .cpp file
108  * The \c FactorDef s are printed in the format:
109  * \code
110  * <unit_name> <unit_value>
111  * \endcode
112  *
113  * @arg ast \c ast::Program to look for \c FactorDef s
114  *
115  * @return std::string Factor definitions
116  */
117 std::string get_factor_definitions(const ast::Program& ast) {
118  std::stringstream ss;
119  const auto& factor_defs = collect_nodes(ast, {ast::AstNodeType::FACTOR_DEF});
120  for (const auto& factor_def: factor_defs) {
121  const auto& factor_def_cast = std::dynamic_pointer_cast<const ast::FactorDef>(factor_def);
122  ss << fmt::format("{} {}\n",
123  factor_def_cast->get_node_name(),
124  factor_def_cast->get_value()->get_value());
125  }
126  return ss.str();
127 }
128 
129 SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units]") {
130  GIVEN("UNITS block with different cases of UNITS definitions") {
131  static const std::string nmodl_text = R"(
132  UNITS {
133  (nA) = (nanoamp)
134  (mA) = (milliamp)
135  (mV) = (millivolt)
136  (uS) = (microsiemens)
137  (nS) = (nanosiemens)
138  (pS) = (picosiemens)
139  (umho) = (micromho)
140  (um) = (micrometers)
141  (mM) = (milli/liter)
142  (uM) = (micro/liter)
143  (msM) = (ms mM)
144  (fAm) = (femto amp meter)
145  (newmol) = (1)
146  (M) = (1/liter)
147  (uM1) = (micro M)
148  (mA/cm2) = (nanoamp/cm2)
149  (molar) = (1 / liter)
150  (S ) = (siemens)
151  (mse-1) = (1/millisec)
152  (um3) = (liter/1e15)
153  (molar1) = (/liter)
154  (newdegK) = (degC)
155  FARADAY1 = (faraday) (coulomb)
156  FARADAY2 = (faraday) (kilocoulombs)
157  FARADAY3 = (faraday) (10000 coulomb)
158  pi = (pi) (1)
159  R1 = (k-mole) (joule/degC)
160  R2 = 8.314 (volt-coul/degC)
161  R3 = (mole k) (mV-coulomb/degC)
162  R4 = 8.314 (volt-coul/degK)
163  R5 = 8.314500000000001 (volt coul/kelvin)
164  dummy1 = 123.45 (m 1/sec2)
165  dummy2 = 123.45e3 (millimeters/sec2)
166  dummy3 = 12345e-2 (m/sec2)
167  KTOMV = 0.0853 (mV/degC)
168  B = 0.26 (mM-cm2/mA-ms)
169  TEMP = 25 (degC)
170  toyfuzz = (1) (volt)
171  numbertwo = 2 (1)
172  oldJ = (R-mole) (1) : compute oldJ based on the value of R which is registered in the UnitTable
173  R = 8 (joule/degC) : define a new value for R that should be visible only in the mod file
174  J = (R-mole) (1) : recalculate J. It's value should be the same as oldJ because R shouldn't change in the UnitTable
175  (myR) = (8 joule/degC) : Define my own R and mole and compute myRnew and myJ based on them
176  (mymole) = (6+23)
177  myRnew = (myR) (1)
178  myJ = (myR-mymole) (1)
179  }
180  )";
181 
182  static const std::string unit_definitions = R"(
183  nA 1e-09: sec-1 coul1
184  mA 0.001: sec-1 coul1
185  mV 0.001: m2 kg1 sec-2 coul-1
186  uS 1e-06: m-2 kg-1 sec1 coul2
187  nS 1e-09: m-2 kg-1 sec1 coul2
188  pS 1e-12: m-2 kg-1 sec1 coul2
189  umho 1e-06: m-2 kg-1 sec1 coul2
190  um 1e-06: m1
191  mM 1: m-3
192  uM 0.001: m-3
193  msM 0.001: m-3 sec1
194  fAm 1e-15: m1 sec-1 coul1
195  newmol 1: constant
196  M 1000: m-3
197  uM1 0.001: m-3
198  mA/cm2 1e-05: m-2 sec-1 coul1
199  molar 1000: m-3
200  S 1: m-2 kg-1 sec1 coul2
201  mse-1 1000: sec-1
202  um3 0.001: m3
203  molar1 1000: m-3
204  newdegK 1: K1
205  myR 8: m2 kg1 sec-2 K-1
206  mymole 6e+23: constant
207  )";
208 
209  static const std::string factor_definitions = R"(
210  FARADAY1 0x1.78e555060882cp+16
211  FARADAY2 0x1.81f0fae775425p+6
212  FARADAY3 0x1.34c0c8b92a9b7p+3
213  pi 0x1.921fb54442d18p+1
214  R1 0x1.0a1013e8990bep+3
215  R2 8.314
216  R3 0x1.03d3b37125759p+13
217  R4 8.314
218  R5 8.314500000000001
219  dummy1 123.45
220  dummy2 123.45e3
221  dummy3 12345e-2
222  KTOMV 0.0853
223  B 0.26
224  TEMP 25
225  toyfuzz 1
226  numbertwo 2
227  oldJ 0x1.0912acba81b67p+82
228  R 8
229  J 0x1.0912acba81b67p+82
230  myRnew 8
231  myJ 0x1.fc3842bd1f072p+81
232  )";
233 
234  THEN("Print the units that were added") {
235  const std::string input(reindent_text(nmodl_text));
236  auto expected_result_unit_definitions = reindent_text(unit_definitions);
237  auto expected_result_factor_definitions = reindent_text(factor_definitions);
238  const auto& [ast, unit_table] = run_units_visitor(input);
239  const auto& generated_unit_definitions = get_unit_definitions(*ast, *unit_table);
240  const auto& generated_factor_definitions = get_factor_definitions(*ast);
241  auto reindented_result_unit_definitions = reindent_text(generated_unit_definitions);
242  auto reindented_result_factor_definitions = reindent_text(generated_factor_definitions);
243  REQUIRE(reindented_result_unit_definitions == expected_result_unit_definitions);
244  REQUIRE(reindented_result_factor_definitions == expected_result_factor_definitions);
245  }
246  }
247  GIVEN("UNITS block with Unit definition which is already defined") {
248  static const std::string nmodl_text = R"(
249  UNITS {
250  (R) = (8 joule/degC)
251  }
252  )";
253  THEN("Throw redefinition exception") {
254  const std::string input(reindent_text(nmodl_text));
255  REQUIRE_THROWS(run_units_visitor(input));
256  }
257  }
258 }
nmodl::parser::UnitDriver::table
std::shared_ptr< nmodl::units::UnitTable > table
shared pointer to the UnitTable that stores all the unit definitions
Definition: unit_driver.hpp:52
test_utils.hpp
nmodl::parser::NmodlDriver
Class that binds all pieces together for parsing nmodl file.
Definition: nmodl_driver.hpp:67
nmodl::units::UnitTable::get_base_unit_name
const std::string & get_base_unit_name(int id) const noexcept
Get base unit name based on the ID number of the dimension.
Definition: units.hpp:288
nmodl::test_utils::reindent_text
std::string reindent_text(const std::string &text, int indent_level)
Reindent nmodl text for text-to-text comparison.
Definition: test_utils.cpp:53
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
get_factor_definitions
std::string get_factor_definitions(const ast::Program &ast)
Returns all the FactorDef s of the ast::Program.
Definition: units.cpp:117
SCENARIO
SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units]")
Definition: units.cpp:129
program.hpp
Auto generated AST classes declaration.
driver
nmodl::parser::UnitDriver driver
Definition: parser.cpp:28
nmodl::ast::AstNodeType::UNIT_DEF
@ UNIT_DEF
type of ast::UnitDef
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
nmodl::units::UnitTable::get_unit
const std::shared_ptr< Unit > & get_unit(const std::string &unit_name) const
Get the unit_name of the UnitTable's table.
Definition: units.hpp:274
units_visitor.hpp
Visitor for Units blocks of AST.
nmodl::collect_nodes
std::vector< std::shared_ptr< const ast::Ast > > collect_nodes(const ast::Ast &node, const std::vector< ast::AstNodeType > &types)
traverse node recursively and collect nodes of given types
Definition: visitor_utils.cpp:206
nmodl::units::UnitTable
Class that stores all the units, prefixes and names of base units used.
Definition: units.hpp:240
nmodl::ast::AstNodeType::FACTOR_DEF
@ FACTOR_DEF
type of ast::FactorDef
factor_def.hpp
Auto generated AST classes declaration.
checkparent_visitor.hpp
Visitor for checking parents of ast nodes
nmodl_driver.hpp
config.h
Version information and units file path.
double.hpp
Auto generated AST classes declaration.
get_unit_definitions
std::string get_unit_definitions(const ast::Program &ast, const units::UnitTable &unit_table)
Returns all the UnitDef s of the ast::Program.
Definition: units.cpp:74
nmodl::ast::Program
Represents top level AST node for whole NMODL input.
Definition: program.hpp:39
nmodl::parser::UnitDriver
Class that binds all pieces together for parsing C units.
Definition: unit_driver.hpp:39
nmodl_constructs.hpp
fmt.h
run_units_visitor
std::tuple< std::shared_ptr< ast::Program >, std::shared_ptr< units::UnitTable > > run_units_visitor(const std::string &text)
Definition: units.cpp:32
nmodl::NrnUnitsLib::get_path
static std::string get_path()
Return path of units database file.
Definition: config.h:54