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 <algorithm>
9 #include <array>
10 #include <cassert>
11 #include <fstream>
12 #include <iostream>
13 #include <map>
14 #include <regex>
15 #include <sstream>
16 #include <string>
17 #include <vector>
18 
19 #include "units.hpp"
20 #include "utils/fmt.h"
21 
22 /**
23  * \file
24  * \brief Units processing while being processed from lexer and parser
25  */
26 
27 namespace nmodl {
28 namespace units {
29 
30 Prefix::Prefix(std::string name, const std::string& factor) {
31  if (name.back() == '-') {
32  name.pop_back();
33  }
34  prefix_name = name;
35  prefix_factor = std::stod(factor);
36 }
37 
38 void Unit::add_unit(const std::string& name) {
39  unit_name = name;
40 }
41 
42 void Unit::add_base_unit(const std::string& name) {
43  // name = "*[a-j]*" which is a base unit
44  const auto dim_name = name[1];
45  const int dim_no = dim_name - 'a';
46  assert(dim_no >= 0 && dim_no < unit_dimensions.size());
47  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
48  unit_dimensions[dim_no] = 1;
49  add_nominator_unit(name);
50 }
51 
52 void Unit::add_nominator_double(const std::string& double_string) {
53  unit_factor = parse_double(double_string);
54 }
55 
56 void Unit::add_nominator_dims(const std::array<int, MAX_DIMS>& dimensions) {
57  std::transform(unit_dimensions.begin(),
58  unit_dimensions.end(),
59  dimensions.begin(),
60  unit_dimensions.begin(),
61  std::plus<int>());
62 }
63 
64 void Unit::add_denominator_dims(const std::array<int, MAX_DIMS>& dimensions) {
65  std::transform(unit_dimensions.begin(),
66  unit_dimensions.end(),
67  dimensions.begin(),
68  unit_dimensions.begin(),
69  std::minus<int>());
70 }
71 
72 void Unit::add_nominator_unit(const std::string& nom) {
73  nominator.push_back(nom);
74 }
75 
76 void Unit::add_nominator_unit(const std::shared_ptr<std::vector<std::string>>& nom) {
77  nominator.insert(nominator.end(), nom->begin(), nom->end());
78 }
79 
80 void Unit::add_denominator_unit(const std::string& denom) {
81  denominator.push_back(denom);
82 }
83 
84 void Unit::add_denominator_unit(const std::shared_ptr<std::vector<std::string>>& denom) {
85  denominator.insert(denominator.end(), denom->begin(), denom->end());
86 }
87 
88 void Unit::mul_factor(const double double_factor) {
89  unit_factor *= double_factor;
90 }
91 
92 void Unit::add_fraction(const std::string& nominator, const std::string& denominator) {
93  double nom = parse_double(nominator);
94  double denom = parse_double(denominator);
95  unit_factor = nom / denom;
96 }
97 
98 /**
99  * Double numbers in the \c nrnunits.lib file are defined in the form [.0-9]+[-+][0-9]+
100  * To make sure they are parsed in the correct way and similarly to the NEURON parser
101  * we convert this string to a string compliant to scientific notation to be able to be parsed
102  * from std::stod(). To do that we have to add an `e` in case there is `-+` in the number
103  *string if it doesn't exist.
104  */
105 double Unit::parse_double(std::string double_string) {
106  auto pos = double_string.find_first_of("+-", 1); // start at 1 pos, because we don't care with
107  // the front sign
108  if (pos != std::string::npos && double_string.find_last_of("eE", pos) == std::string::npos) {
109  double_string.insert(pos, "e");
110  }
111  return std::stod(double_string);
112 }
113 
114 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
115 void UnitTable::calc_nominator_dims(const std::shared_ptr<Unit>& unit, std::string nominator_name) {
116  double nominator_prefix_factor = 1.0;
117  int nominator_power = 1;
118 
119  // if the nominator is DOUBLE, divide it from the unit factor
120  if (nominator_name.front() >= '1' && nominator_name.front() <= '9') {
121  unit->mul_factor(1 / std::stod(nominator_name));
122  return;
123  }
124 
125  std::string nom_name = nominator_name;
126  auto nominator = table.find(nominator_name);
127 
128  // if the nominator_name is not in the table, check if there are any prefixes or power
129  if (nominator == table.end()) {
130  int changed_nominator_name = 1;
131 
132  while (changed_nominator_name) {
133  changed_nominator_name = 0;
134  for (const auto& it: prefixes) {
135  auto res = std::mismatch(it.first.begin(),
136  it.first.end(),
137  nominator_name.begin(),
138  nominator_name.end());
139  if (res.first == it.first.end()) {
140  changed_nominator_name = 1;
141  nominator_prefix_factor *= it.second;
142  nominator_name.erase(nominator_name.begin(),
143  nominator_name.begin() +
144  static_cast<std::ptrdiff_t>(it.first.size()));
145  }
146  }
147  }
148  // if the nominator is only a prefix, just multiply the prefix factor with the unit factor
149  if (nominator_name.empty()) {
150  for (const auto& it: prefixes) {
151  auto res = std::mismatch(it.first.begin(), it.first.end(), nom_name.begin());
152  if (res.first == it.first.end()) {
153  unit->mul_factor(it.second);
154  return;
155  }
156  }
157  }
158 
159  // if the nominator_back is a UNIT_POWER save the power to be able
160  // to calculate the correct factor and dimensions later
161  char nominator_back = nominator_name.back();
162  if (nominator_back >= '2' && nominator_back <= '9') {
163  nominator_power = nominator_back - '0';
164  nominator_name.pop_back();
165  }
166 
167  nominator = table.find(nominator_name);
168 
169  // delete "s" char for plural of the nominator_name
170  if (nominator == table.end()) {
171  if (nominator_name.back() == 's') {
172  nominator_name.pop_back();
173  }
174  nominator = table.find(nominator_name);
175  }
176  }
177 
178  // if the nominator is still not found in the table then output error
179  // else multiply its factor to the unit factor and calculate unit's dimensions
180  if (nominator == table.end()) {
181  std::string ss = fmt::format("Unit {} not defined!", nominator_name);
182  throw std::runtime_error(ss);
183  } else {
184  for (int i = 0; i < nominator_power; i++) {
185  unit->mul_factor(nominator_prefix_factor * nominator->second->get_factor());
186  unit->add_nominator_dims(nominator->second->get_dimensions());
187  }
188  }
189 }
190 
191 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
192 void UnitTable::calc_denominator_dims(const std::shared_ptr<Unit>& unit,
193  std::string denominator_name) {
194  double denominator_prefix_factor = 1.0;
195  int denominator_power = 1;
196 
197  // if the denominator is DOUBLE, divide it from the unit factor
198  if (denominator_name.front() >= '1' && denominator_name.front() <= '9') {
199  unit->mul_factor(std::stod(denominator_name));
200  return;
201  }
202 
203  std::string denom_name = denominator_name;
204  auto denominator = table.find(denominator_name);
205 
206  // if the denominator_name is not in the table, check if there are any prefixes or power
207  if (denominator == table.end()) {
208  bool changed_denominator_name = true;
209 
210  while (changed_denominator_name) {
211  changed_denominator_name = false;
212  for (const auto& it: prefixes) {
213  auto res = std::mismatch(it.first.begin(),
214  it.first.end(),
215  denominator_name.begin(),
216  denominator_name.end());
217  if (res.first == it.first.end()) {
218  changed_denominator_name = true;
219  denominator_prefix_factor *= it.second;
220  denominator_name.erase(denominator_name.begin(),
221  denominator_name.begin() +
222  static_cast<std::ptrdiff_t>(it.first.size()));
223  }
224  }
225  }
226  // if the denominator is only a prefix, just multiply the prefix factor with the unit factor
227  if (denominator_name.empty()) {
228  for (const auto& it: prefixes) {
229  auto res = std::mismatch(it.first.begin(), it.first.end(), denom_name.begin());
230  if (res.first == it.first.end()) {
231  unit->mul_factor(it.second);
232  return;
233  }
234  }
235  }
236 
237  // if the denominator_back is a UNIT_POWER save the power to be able
238  // to calculate the correct factor and dimensions later
239  char denominator_back = denominator_name.back();
240  if (denominator_back >= '2' && denominator_back <= '9') {
241  denominator_power = denominator_back - '0';
242  denominator_name.pop_back();
243  }
244 
245  denominator = table.find(denominator_name);
246 
247  // delete "s" char for plural of the denominator_name
248  if (denominator == table.end()) {
249  if (denominator_name.back() == 's') {
250  denominator_name.pop_back();
251  }
252  denominator = table.find(denominator_name);
253  }
254  }
255 
256  if (denominator == table.end()) {
257  std::string ss = fmt::format("Unit {} not defined!", denominator_name);
258  throw std::runtime_error(ss);
259  } else {
260  for (int i = 0; i < denominator_power; i++) {
261  unit->mul_factor(1.0 / (denominator_prefix_factor * denominator->second->get_factor()));
262  unit->add_denominator_dims(denominator->second->get_dimensions());
263  }
264  }
265 }
266 
267 void UnitTable::insert(const std::shared_ptr<Unit>& unit) {
268  // if the unit is already in the table throw error because
269  // redefinition of a unit is not allowed
270  if (table.find(unit->get_name()) != table.end()) {
271  std::stringstream ss_unit_string;
272  ss_unit_string << fmt::format("{:g} {}",
273  unit->get_factor(),
274  fmt::join(unit->get_nominator_unit(), ""));
275  if (!unit->get_denominator_unit().empty()) {
276  ss_unit_string << fmt::format("/{}", fmt::join(unit->get_denominator_unit(), ""));
277  }
278  throw std::runtime_error(fmt::format("Redefinition of units ({}) to {} is not allowed.",
279  unit->get_name(),
280  ss_unit_string.str()));
281  }
282  // check if the unit is a base unit and
283  // then add it to the base units vector
284  auto unit_nominator = unit->get_nominator_unit();
285  auto only_base_unit_nominator =
286  unit_nominator.size() == 1 && unit_nominator.front().size() == 3 &&
287  (unit_nominator.front().front() == '*' && unit_nominator.front().back() == '*');
288  if (only_base_unit_nominator) {
289  // base_units_names[i] = "*i-th base unit*" (ex. base_units_names[0] = "*a*")
290  auto const index = unit_nominator.front()[1] - 'a';
291  assert(index >= 0 && index < base_units_names.size());
292  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
293  base_units_names[index] = unit->get_name();
294  table.insert({unit->get_name(), unit});
295  return;
296  }
297  // calculate unit's dimensions based on its nominator and denominator
298  for (const auto& it: unit->get_nominator_unit()) {
299  calc_nominator_dims(unit, it);
300  }
301  for (const auto& it: unit->get_denominator_unit()) {
302  calc_denominator_dims(unit, it);
303  }
304  table.insert({unit->get_name(), unit});
305 }
306 
307 void UnitTable::insert_prefix(const std::shared_ptr<Prefix>& prfx) {
308  prefixes.insert({prfx->get_name(), prfx->get_factor()});
309 }
310 
311 void UnitTable::print_units_sorted(std::ostream& units_details) const {
312  std::vector<std::pair<std::string, std::shared_ptr<Unit>>> sorted_elements(table.begin(),
313  table.end());
314  std::sort(sorted_elements.begin(), sorted_elements.end());
315  for (const auto& it: sorted_elements) {
316  units_details << fmt::format("{} {:g}: {}\n",
317  it.first,
318  it.second->get_factor(),
319  fmt::join(it.second->get_dimensions(), " "));
320  }
321 }
322 
323 void UnitTable::print_base_units(std::ostream& base_units_details) const {
324  bool first_print = true;
325  for (const auto& it: base_units_names) {
326  if (!it.empty()) {
327  if (first_print) {
328  first_print = false;
329  base_units_details << it;
330  } else {
331  base_units_details << ' ' << it;
332  }
333  }
334  }
335  base_units_details << '\n';
336 }
337 
338 } // namespace units
339 } // namespace nmodl
nmodl::units::Unit::add_nominator_unit
void add_nominator_unit(const std::string &nom)
Add a unit to the vector of nominator strings of the Unit, so it can be processed later.
Definition: units.cpp:72
nmodl::units::UnitTable::table
std::unordered_map< std::string, std::shared_ptr< Unit > > table
Hash map that stores all the Units.
Definition: units.hpp:243
nmodl::units::Unit::unit_dimensions
std::array< int, MAX_DIMS > unit_dimensions
Array of MAX_DIMS size that keeps the Unit's dimensions.
Definition: units.hpp:79
nmodl::units::UnitTable::prefixes
std::unordered_map< std::string, double > prefixes
Hash map that stores all the Prefixes.
Definition: units.hpp:246
nmodl::units::Prefix::prefix_factor
double prefix_factor
Prefix's double factor.
Definition: units.hpp:190
nmodl::units::UnitTable::insert
void insert(const std::shared_ptr< Unit > &unit)
Insert a unit to the UnitTable table and calculate its dimensions and factor based on the previously ...
Definition: units.cpp:267
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
nmodl::units::Unit::add_nominator_dims
void add_nominator_dims(const std::array< int, MAX_DIMS > &dimensions)
Add the dimensions of a nominator of the unit to the dimensions of the Unit.
Definition: units.cpp:56
nmodl::units::Unit::add_denominator_dims
void add_denominator_dims(const std::array< int, MAX_DIMS > &dimensions)
Subtract the dimensions of a nominator of the unit to the dimensions of the Unit.
Definition: units.cpp:64
nmodl::units::Unit::unit_factor
double unit_factor
Double factor of the Unit.
Definition: units.hpp:76
nmodl::units::Unit::mul_factor
void mul_factor(double double_factor)
Multiply Unit's factor with a double factor.
Definition: units.cpp:88
nmodl::units::Unit::nominator
std::vector< std::string > nominator
Vector of nominators of the Unit.
Definition: units.hpp:85
nmodl::units::Prefix::Prefix
Prefix()=delete
Default constructor for Prefix.
units.hpp
Classes for storing different units specification.
nmodl::units::Unit::add_unit
void add_unit(const std::string &name)
Add unit name to the Unit.
Definition: units.cpp:38
nmodl::units::UnitTable::base_units_names
std::array< std::string, MAX_DIMS > base_units_names
Hash map that stores all the base units' names.
Definition: units.hpp:249
nmodl::units::Unit::unit_name
std::string unit_name
Name of the Unit.
Definition: units.hpp:82
nmodl::units::UnitTable::calc_denominator_dims
void calc_denominator_dims(const std::shared_ptr< Unit > &unit, std::string denominator_name)
Calculate unit's dimensions based on its denominator unit named denominator_name which is stored in t...
Definition: units.cpp:192
nmodl::units::Prefix::prefix_name
std::string prefix_name
Prefix's name.
Definition: units.hpp:193
nmodl::units::UnitTable::print_units_sorted
void print_units_sorted(std::ostream &units_details) const
Print the details of the units that are stored in the UnitTable to the output stream units_details in...
Definition: units.cpp:311
nmodl::units::UnitTable::calc_nominator_dims
void calc_nominator_dims(const std::shared_ptr< Unit > &unit, std::string nominator_name)
Calculate unit's dimensions based on its nominator unit named nominator_name which is stored in the U...
Definition: units.cpp:115
nmodl::units::UnitTable::print_base_units
void print_base_units(std::ostream &base_units_details) const
Print the base units that are stored in the UnitTable to the output stream base_units_details.
Definition: units.cpp:323
nmodl::units::Unit::add_denominator_unit
void add_denominator_unit(const std::string &denom)
Add a unit to the vector of denominator strings of the Unit, so it can be processed later.
Definition: units.cpp:80
nmodl::units::UnitTable::insert_prefix
void insert_prefix(const std::shared_ptr< Prefix > &prfx)
Insert a prefix to the prefixes of the UnitTable.
Definition: units.cpp:307
nmodl::units::Unit::denominator
std::vector< std::string > denominator
Vector of denominators of the Unit.
Definition: units.hpp:88
nmodl::units::Unit::add_fraction
void add_fraction(const std::string &nominator, const std::string &denominator)
Parse a fraction given as string and store the result to the factor of the Unit.
Definition: units.cpp:92
nmodl::units::Unit::parse_double
static double parse_double(std::string double_string)
Parse a double number given as string.
Definition: units.cpp:105
nmodl::units::Unit::add_nominator_double
void add_nominator_double(const std::string &double_string)
Takes as argument a double as string, parses it as double and stores it to the Unit factor.
Definition: units.cpp:52
nmodl::units::Unit::add_base_unit
void add_base_unit(const std::string &name)
If the Unit is a base unit the dimensions of the Unit should be calculated based on the name of the b...
Definition: units.cpp:42
fmt.h