• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 21492883686

29 Jan 2026 08:02PM UTC coverage: 81.749% (-0.2%) from 81.993%
21492883686

Pull #3756

github

web-flow
Merge 7d830ab98 into f7a734189
Pull Request #3756: Refactor ParticleType to use PDG Monte Carlo numbering scheme

17298 of 24251 branches covered (71.33%)

Branch coverage included in aggregate %.

346 of 501 new or added lines in 32 files covered. (69.06%)

453 existing lines in 6 files now uncovered.

55889 of 65276 relevant lines covered (85.62%)

44298457.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.12
/src/particle_type.cpp
1
#include "openmc/particle_type.h"
2

3
#include <algorithm>
4
#include <cctype>
5
#include <stdexcept>
6

7
#include "openmc/string_utils.h"
8

9
namespace openmc {
10
namespace {
11

12
constexpr const char* ATOMIC_SYMBOL[] = {"", "H", "He", "Li", "Be", "B", "C",
13
  "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca",
14
  "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As",
15
  "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd",
16
  "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr",
17
  "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf",
18
  "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At",
19
  "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf",
20
  "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg",
21
  "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og"};
22

23
constexpr int MAX_Z =
24
  static_cast<int>(sizeof(ATOMIC_SYMBOL) / sizeof(ATOMIC_SYMBOL[0])) - 1;
25

NEW
26
bool is_integer_string(const std::string& s)
×
27
{
NEW
28
  if (s.empty())
×
NEW
29
    return false;
×
NEW
30
  size_t i = 0;
×
NEW
31
  if (s[0] == '-' || s[0] == '+') {
×
NEW
32
    if (s.size() == 1)
×
NEW
33
      return false;
×
NEW
34
    i = 1;
×
35
  }
NEW
36
  for (; i < s.size(); ++i) {
×
NEW
37
    if (!std::isdigit(static_cast<unsigned char>(s[i])))
×
NEW
38
      return false;
×
39
  }
NEW
40
  return true;
×
41
}
42

NEW
43
int atomic_number_from_symbol(std::string_view symbol)
×
44
{
NEW
45
  for (int z = 1; z <= MAX_Z; ++z) {
×
NEW
46
    if (symbol == ATOMIC_SYMBOL[z]) {
×
NEW
47
      return z;
×
48
    }
49
  }
NEW
50
  return 0;
×
51
}
52

NEW
53
bool parse_gnds_nuclide(std::string_view name, int& Z, int& A, int& m)
×
54
{
NEW
55
  if (name.empty())
×
NEW
56
    return false;
×
57

NEW
58
  size_t pos = 0;
×
NEW
59
  if (!std::isupper(static_cast<unsigned char>(name[pos])))
×
NEW
60
    return false;
×
61

NEW
62
  std::string symbol;
×
NEW
63
  symbol += name[pos++];
×
NEW
64
  if (pos < name.size() &&
×
NEW
65
      std::islower(static_cast<unsigned char>(name[pos]))) {
×
NEW
66
    symbol += name[pos++];
×
67
  }
68

NEW
69
  if (pos >= name.size() ||
×
NEW
70
      !std::isdigit(static_cast<unsigned char>(name[pos]))) {
×
NEW
71
    return false;
×
72
  }
73

NEW
74
  size_t a_start = pos;
×
NEW
75
  while (
×
NEW
76
    pos < name.size() && std::isdigit(static_cast<unsigned char>(name[pos]))) {
×
NEW
77
    ++pos;
×
78
  }
NEW
79
  A = std::stoi(std::string {name.substr(a_start, pos - a_start)});
×
NEW
80
  if (A <= 0 || A > 999)
×
NEW
81
    return false;
×
82

NEW
83
  m = 0;
×
NEW
84
  if (pos < name.size()) {
×
NEW
85
    if (name[pos] != '_' || pos + 2 >= name.size() || name[pos + 1] != 'm') {
×
NEW
86
      return false;
×
87
    }
NEW
88
    pos += 2;
×
NEW
89
    size_t m_start = pos;
×
NEW
90
    while (pos < name.size() &&
×
NEW
91
           std::isdigit(static_cast<unsigned char>(name[pos]))) {
×
NEW
92
      ++pos;
×
93
    }
NEW
94
    if (m_start == pos)
×
NEW
95
      return false;
×
NEW
96
    m = std::stoi(std::string {name.substr(m_start, pos - m_start)});
×
NEW
97
    if (m < 0 || m > 9)
×
NEW
98
      return false;
×
99
  }
100

NEW
101
  if (pos != name.size())
×
NEW
102
    return false;
×
103

NEW
104
  Z = atomic_number_from_symbol(symbol);
×
NEW
105
  return Z != 0;
×
NEW
106
}
×
107

108
// Helper to convert nuclear PDG number to nuclide name
NEW
109
std::string nuclide_name_from_pdg(int32_t pdg)
×
110
{
NEW
111
  int32_t code = pdg;
×
NEW
112
  int m = code % 10;
×
NEW
113
  int A = (code / 10) % 1000;
×
NEW
114
  int Z = (code / 10000) % 1000;
×
115

NEW
116
  if (Z <= 0 || Z > MAX_Z || A <= 0 || A > 999) {
×
NEW
117
    throw std::invalid_argument {
×
NEW
118
      "Invalid nuclear PDG number: " + std::to_string(pdg)};
×
119
  }
120

NEW
121
  std::string name = ATOMIC_SYMBOL[Z] + std::to_string(A);
×
NEW
122
  if (m > 0) {
×
NEW
123
    name += "_m" + std::to_string(m);
×
124
  }
NEW
125
  return name;
×
NEW
126
}
×
127

128
} // namespace
129

130
//==============================================================================
131
// ParticleType member function implementations
132
//==============================================================================
133

134
ParticleType::ParticleType(std::string_view str)
3,361,120✔
135
{
136
  std::string s {str};
3,361,120✔
137
  strtrim(s);
3,361,120✔
138
  if (s.empty()) {
3,361,120!
NEW
139
    throw std::invalid_argument {"Particle string is empty."};
×
140
  }
141

142
  std::string lower = s;
3,361,120✔
143
  to_lower(lower);
3,361,120✔
144

145
  // Check for pdg: prefix
146
  if (starts_with(lower, "pdg:")) {
3,361,120!
NEW
147
    std::string value_str = lower.substr(4);
×
NEW
148
    if (!is_integer_string(value_str)) {
×
NEW
149
      throw std::invalid_argument {"Invalid PDG number: " + value_str};
×
150
    }
NEW
151
    pdg_number_ = std::stoi(value_str);
×
NEW
152
    return;
×
NEW
153
  }
×
154

155
  // Check for known particle names
156
  if (lower == "neutron" || lower == "n") {
3,361,120!
157
    pdg_number_ = PDG_NEUTRON;
818,975✔
158
    return;
818,975✔
159
  }
160
  if (lower == "photon" || lower == "gamma") {
2,542,145!
161
    pdg_number_ = PDG_PHOTON;
2,542,043✔
162
    return;
2,542,043✔
163
  }
164
  if (lower == "electron") {
102✔
165
    pdg_number_ = PDG_ELECTRON;
59✔
166
    return;
59✔
167
  }
168
  if (lower == "positron") {
43!
169
    pdg_number_ = PDG_POSITRON;
43✔
170
    return;
43✔
171
  }
NEW
172
  if (lower == "proton" || lower == "p" || lower == "h1") {
×
NEW
173
    pdg_number_ = PDG_PROTON;
×
NEW
174
    return;
×
175
  }
NEW
176
  if (lower == "deuteron" || lower == "d" || lower == "h2") {
×
NEW
177
    pdg_number_ = PDG_DEUTERON;
×
NEW
178
    return;
×
179
  }
NEW
180
  if (lower == "triton" || lower == "t" || lower == "h3") {
×
NEW
181
    pdg_number_ = PDG_TRITON;
×
NEW
182
    return;
×
183
  }
NEW
184
  if (lower == "alpha" || lower == "he4") {
×
NEW
185
    pdg_number_ = PDG_ALPHA;
×
NEW
186
    return;
×
187
  }
188

189
  // Check for integer string
NEW
190
  if (is_integer_string(s)) {
×
NEW
191
    pdg_number_ = std::stoi(s);
×
NEW
192
    return;
×
193
  }
194

195
  // Try to parse as GNDS nuclide name
NEW
196
  int Z = 0;
×
NEW
197
  int A = 0;
×
NEW
198
  int m = 0;
×
NEW
199
  if (!parse_gnds_nuclide(s, Z, A, m)) {
×
NEW
200
    throw std::invalid_argument {"Invalid nuclide name: " + s};
×
201
  }
NEW
202
  pdg_number_ = 1000000000 + Z * 10000 + A * 10 + m;
×
203
}
6,722,240!
204

205
std::string ParticleType::str() const
3,130,307✔
206
{
207
  if (pdg_number_ == PDG_NEUTRON)
3,130,307✔
208
    return "neutron";
2,399,940✔
209
  if (pdg_number_ == PDG_PHOTON)
730,367✔
210
    return "photon";
730,103✔
211
  if (pdg_number_ == PDG_ELECTRON)
264✔
212
    return "electron";
132✔
213
  if (pdg_number_ == PDG_POSITRON)
132!
214
    return "positron";
132✔
NEW
215
  if (pdg_number_ == PDG_PROTON)
×
NEW
216
    return "proton";
×
217

NEW
218
  if (is_nucleus()) {
×
NEW
219
    return nuclide_name_from_pdg(pdg_number_);
×
220
  }
221

NEW
222
  return "pdg:" + std::to_string(pdg_number_);
×
223
}
224

225
//==============================================================================
226
// Free function implementations
227
//==============================================================================
228

NEW
229
ParticleType legacy_particle_index_to_type(int index)
×
230
{
NEW
231
  switch (index) {
×
NEW
232
  case 0:
×
NEW
233
    return ParticleType {PDG_NEUTRON};
×
NEW
234
  case 1:
×
NEW
235
    return ParticleType {PDG_PHOTON};
×
NEW
236
  case 2:
×
NEW
237
    return ParticleType {PDG_ELECTRON};
×
NEW
238
  case 3:
×
NEW
239
    return ParticleType {PDG_POSITRON};
×
NEW
240
  default:
×
NEW
241
    throw std::invalid_argument {
×
NEW
242
      "Invalid legacy particle index: " + std::to_string(index)};
×
243
  }
244
}
245

246
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc