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

openmc-dev / openmc / 35914988175

23 Sep 2026 08:17PM UTC coverage: 81.584% (+0.1%) from 81.485%
35914988175

Pull #4141

github

web-flow
Merge bae6ac850 into 1d75981db
Pull Request #4141: Adding multigroup photon transport capability in MC mode

20033 of 29057 branches covered (68.94%)

Branch coverage included in aggregate %.

186 of 216 new or added lines in 10 files covered. (86.11%)

8 existing lines in 5 files now uncovered.

62689 of 72338 relevant lines covered (86.66%)

49543044.08 hits per line

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

93.69
/src/xsdata.cpp
1
#include "openmc/xsdata.h"
2

3
#include <algorithm>
4
#include <cmath>
5
#include <cstdlib>
6
#include <numeric>
7

8
#include "openmc/tensor.h"
9

10
#include "openmc/constants.h"
11
#include "openmc/error.h"
12
#include "openmc/math_functions.h"
13
#include "openmc/mgxs_interface.h"
14
#include "openmc/random_lcg.h"
15
#include "openmc/settings.h"
16

17
namespace openmc {
18

19
//==============================================================================
20
// XsData class methods
21
//==============================================================================
22

23
XsData::XsData(bool fissionable, AngleDistributionType scatter_format,
8,011 ✔
24
  int n_pol, int n_azi, size_t n_groups, size_t n_d_groups)
8,011 ✔
25
  : n_g_(n_groups), n_dg_(n_d_groups)
8,011 ✔
26
{
27
  size_t n_ang = n_pol * n_azi;
8,011 ✔
28

29
  // check to make sure scatter format is OK before we allocate
30
  if (scatter_format != AngleDistributionType::HISTOGRAM &&
8,011 ✔
31
      scatter_format != AngleDistributionType::TABULAR &&
8,011 !
32
      scatter_format != AngleDistributionType::LEGENDRE) {
33
    fatal_error("Invalid scatter_format!");
×
34
  }
35
  // allocate all [temperature][angle][in group] quantities
36
  vector<size_t> shape {n_ang, n_g_};
8,011 ✔
37
  total = tensor::zeros<double>(shape);
8,011 ✔
38
  absorption = tensor::zeros<double>(shape);
8,011 ✔
39
  inverse_velocity = tensor::zeros<double>(shape);
8,011 ✔
40
  if (fissionable) {
8,011 ✔
41
    fission = tensor::zeros<double>(shape);
2,497 ✔
42
    nu_fission = tensor::zeros<double>(shape);
2,497 ✔
43
    prompt_nu_fission = tensor::zeros<double>(shape);
2,497 ✔
44
    kappa_fission = tensor::zeros<double>(shape);
4,994 ✔
45
  }
46

47
  // allocate decay_rate; [temperature][angle][delayed group]
48
  shape[1] = n_dg_;
8,011 ✔
49
  decay_rate = tensor::zeros<double>(shape);
8,011 ✔
50

51
  if (fissionable) {
8,011 ✔
52
    shape = {n_ang, n_dg_, n_g_};
2,497 ✔
53
    // allocate delayed_nu_fission; [temperature][angle][delay group][in group]
54
    delayed_nu_fission = tensor::zeros<double>(shape);
2,497 ✔
55

56
    // chi_prompt; [temperature][angle][in group][out group]
57
    shape = {n_ang, n_g_, n_g_};
2,497 ✔
58
    chi_prompt = tensor::zeros<double>(shape);
2,497 ✔
59

60
    // chi_delayed; [temperature][angle][delay group][in group][out group]
61
    shape = {n_ang, n_dg_, n_g_, n_g_};
2,497 ✔
62
    chi_delayed = tensor::zeros<double>(shape);
4,994 ✔
63
  }
64

65
  for (int a = 0; a < n_ang; a++) {
16,202 ✔
66
    if (scatter_format == AngleDistributionType::HISTOGRAM) {
8,191 ✔
67
      scatter.emplace_back(new ScattDataHistogram);
120 ✔
68
    } else if (scatter_format == AngleDistributionType::TABULAR) {
8,071 ✔
69
      scatter.emplace_back(new ScattDataTabular);
7,531 ✔
70
    } else if (scatter_format == AngleDistributionType::LEGENDRE) {
540 ✔
71
      scatter.emplace_back(new ScattDataLegendre);
540 ✔
72
    }
73
  }
74
}
8,011 ✔
75

76
//==============================================================================
77

78
void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable,
4,268 ✔
79
  AngleDistributionType scatter_format,
80
  AngleDistributionType final_scatter_format, int order_data, bool is_isotropic,
81
  int n_pol, int n_azi)
82
{
83
  // Reconstruct the dimension information so it doesn't need to be passed
84
  size_t n_ang = n_pol * n_azi;
4,268 ✔
85
  size_t energy_groups = total.shape(1);
4,268 !
86

87
  // Set the fissionable-specific data
88
  if (fissionable) {
4,268 ✔
89
    fission_from_hdf5(xsdata_grp, n_ang, is_isotropic);
1,331 ✔
90
  }
91
  // Get the non-fission-specific data
92
  read_nd_tensor(xsdata_grp, "decay-rate", decay_rate);
4,268 ✔
93
  read_nd_tensor(xsdata_grp, "absorption", absorption, true);
4,268 ✔
94
  read_nd_tensor(xsdata_grp, "inverse-velocity", inverse_velocity);
4,268 ✔
95

96
  // Get scattering data
97
  scatter_from_hdf5(
4,268 ✔
98
    xsdata_grp, n_ang, scatter_format, final_scatter_format, order_data);
99

100
  // Replace zero absorption values with a small number to avoid
101
  // division by zero in tally methods
102
  for (size_t i = 0; i < absorption.size(); i++)
20,585 ✔
103
    if (absorption.data()[i] == 0.0)
16,317 ✔
104
      absorption.data()[i] = 1.e-10;
22 ✔
105

106
  // Get or calculate the total x/s
107
  if (object_exists(xsdata_grp, "total")) {
4,268 !
108
    read_nd_tensor(xsdata_grp, "total", total);
4,268 ✔
109
  } else {
110
    for (size_t a = 0; a < n_ang; a++) {
×
111
      for (size_t gin = 0; gin < energy_groups; gin++) {
×
112
        total(a, gin) = absorption(a, gin) + scatter[a]->scattxs[gin];
×
113
      }
114
    }
115
  }
116

117
  // Replace zero total cross sections with a small number to avoid
118
  // division by zero in tally methods
119
  for (size_t i = 0; i < total.size(); i++)
20,585 ✔
120
    if (total.data()[i] == 0.0)
16,317 ✔
121
      total.data()[i] = 1.e-10;
22 ✔
122

123
  // Photon libraries fold secondary photons into the scatter matrix and store
124
  // no multiplicity. Derive the group-wise one the MC collision game needs.
125
  if (data::mg.particle_type_.is_photon() &&
4,307 !
126
      !object_exists(xsdata_grp, "scatter_data/multiplicity_matrix")) {
39 ✔
NEW
127
    for (size_t a = 0; a < n_ang; a++) {
×
NEW
128
      for (size_t g = 0; g < energy_groups; g++) {
×
NEW
129
        double production = scatter[a]->scattxs[g];
×
NEW
130
        if (production <= 0.0)
×
NEW
131
          continue;
×
NEW
132
        double removal = total(a, g) - absorption(a, g);
×
NEW
133
        if (removal <= 0.0)
×
NEW
134
          fatal_error(fmt::format("Photon group {} produces photons but has "
×
NEW
135
                                  "no scattering to carry them.", g + 1));
×
NEW
136
        std::fill(scatter[a]->mult[g].begin(), scatter[a]->mult[g].end(),
×
NEW
137
          production / removal);
×
138
      }
139
    }
140
  }
141
}
4,268 ✔
142

143
//==============================================================================
144

145
void XsData::fission_vector_beta_from_hdf5(
45 ✔
146
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
147
{
148
  // Data is provided as nu-fission and chi with a beta for delayed info
149

150
  // Get chi
151
  tensor::Tensor<double> temp_chi = tensor::zeros<double>({n_ang, n_g_});
45 ✔
152
  read_nd_tensor(xsdata_grp, "chi", temp_chi, true);
45 ✔
153

154
  // Normalize chi so it sums to 1 over outgoing groups for each angle
155
  for (size_t a = 0; a < n_ang; a++) {
90 ✔
156
    tensor::View<double> row = temp_chi.slice(a);
45 ✔
157
    row /= row.sum();
45 ✔
158
  }
45 ✔
159

160
  // Replicate the energy spectrum across all incoming groups — the
161
  // spectrum is independent of the incoming neutron energy
162
  for (size_t a = 0; a < n_ang; a++)
90 ✔
163
    for (size_t gin = 0; gin < n_g_; gin++)
135 ✔
164
      chi_prompt.slice(a, gin) = temp_chi.slice(a);
270 ✔
165

166
  // Same spectrum for delayed neutrons, replicated across delayed groups
167
  for (size_t a = 0; a < n_ang; a++)
90 ✔
168
    for (size_t d = 0; d < n_dg_; d++)
195 ✔
169
      for (size_t gin = 0; gin < n_g_; gin++)
450 ✔
170
        chi_delayed.slice(a, d, gin) = temp_chi.slice(a);
900 ✔
171

172
  // Get nu-fission
173
  tensor::Tensor<double> temp_nufiss = tensor::zeros<double>({n_ang, n_g_});
45 ✔
174
  read_nd_tensor(xsdata_grp, "nu-fission", temp_nufiss, true);
45 ✔
175

176
  // Get beta (strategy will depend upon the number of dimensions in beta)
177
  hid_t beta_dset = open_dataset(xsdata_grp, "beta");
45 ✔
178
  int beta_ndims = dataset_ndims(beta_dset);
45 ✔
179
  close_dataset(beta_dset);
45 ✔
180
  int ndim_target = 1;
45 ✔
181
  if (!is_isotropic)
45 !
UNCOV
182
    ndim_target += 2;
×
183
  if (beta_ndims == ndim_target) {
45 ✔
184
    tensor::Tensor<double> temp_beta = tensor::zeros<double>({n_ang, n_dg_});
30 ✔
185
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
30 ✔
186

187
    // prompt_nu_fission = (1 - sum_of_beta) * nu_fission
188
    auto beta_sum = temp_beta.sum(1);
30 ✔
189
    for (size_t a = 0; a < n_ang; a++)
60 ✔
190
      for (size_t g = 0; g < n_g_; g++)
90 ✔
191
        prompt_nu_fission(a, g) = temp_nufiss(a, g) * (1.0 - beta_sum(a));
60 ✔
192

193
    // Delayed nu-fission is the outer product of the delayed neutron
194
    // fraction (beta) and the fission production rate (nu-fission)
195
    for (size_t a = 0; a < n_ang; a++)
60 ✔
196
      for (size_t d = 0; d < n_dg_; d++)
150 ✔
197
        for (size_t g = 0; g < n_g_; g++)
360 ✔
198
          delayed_nu_fission(a, d, g) = temp_beta(a, d) * temp_nufiss(a, g);
240 ✔
199
  } else if (beta_ndims == ndim_target + 1) {
75 !
200
    tensor::Tensor<double> temp_beta =
15 ✔
201
      tensor::zeros<double>({n_ang, n_dg_, n_g_});
15 ✔
202
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
15 ✔
203

204
    // prompt_nu_fission = (1 - sum_of_beta) * nu_fission
205
    // Here beta is energy-dependent, so sum over delayed groups (axis 1)
206
    auto beta_sum = temp_beta.sum(1);
15 ✔
207
    for (size_t a = 0; a < n_ang; a++)
30 ✔
208
      for (size_t g = 0; g < n_g_; g++)
45 ✔
209
        prompt_nu_fission(a, g) = temp_nufiss(a, g) * (1.0 - beta_sum(a, g));
30 ✔
210

211
    // Delayed nu-fission: beta is already energy-dependent [n_ang, n_dg, n_g],
212
    // so scale each delayed group's beta by the total nu-fission for that group
213
    for (size_t a = 0; a < n_ang; a++)
30 ✔
214
      for (size_t d = 0; d < n_dg_; d++)
45 ✔
215
        for (size_t g = 0; g < n_g_; g++)
90 ✔
216
          delayed_nu_fission(a, d, g) = temp_beta(a, d, g) * temp_nufiss(a, g);
60 ✔
217
  }
30 ✔
218
}
90 ✔
219

220
void XsData::fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
15 ✔
221
{
222
  // Data is provided separately as prompt + delayed nu-fission and chi
223

224
  // Get chi-prompt
225
  tensor::Tensor<double> temp_chi_p = tensor::zeros<double>({n_ang, n_g_});
15 ✔
226
  read_nd_tensor(xsdata_grp, "chi-prompt", temp_chi_p, true);
15 ✔
227

228
  // Normalize prompt chi so it sums to 1 over outgoing groups for each angle
229
  for (size_t a = 0; a < n_ang; a++) {
30 ✔
230
    tensor::View<double> row = temp_chi_p.slice(a);
15 ✔
231
    row /= row.sum();
15 ✔
232
  }
15 ✔
233

234
  // Get chi-delayed
235
  tensor::Tensor<double> temp_chi_d =
15 ✔
236
    tensor::zeros<double>({n_ang, n_dg_, n_g_});
15 ✔
237
  read_nd_tensor(xsdata_grp, "chi-delayed", temp_chi_d, true);
15 ✔
238

239
  // Normalize delayed chi so it sums to 1 over outgoing groups for each
240
  // angle and delayed group
241
  for (size_t a = 0; a < n_ang; a++)
30 ✔
242
    for (size_t d = 0; d < n_dg_; d++) {
45 ✔
243
      tensor::View<double> row = temp_chi_d.slice(a, d);
30 ✔
244
      row /= row.sum();
30 ✔
245
    }
30 ✔
246

247
  // Replicate the prompt spectrum across all incoming groups
248
  for (size_t a = 0; a < n_ang; a++)
30 ✔
249
    for (size_t gin = 0; gin < n_g_; gin++)
45 ✔
250
      chi_prompt.slice(a, gin) = temp_chi_p.slice(a);
90 ✔
251

252
  // Replicate the delayed spectrum across all incoming groups
253
  for (size_t a = 0; a < n_ang; a++)
30 ✔
254
    for (size_t d = 0; d < n_dg_; d++)
45 ✔
255
      for (size_t gin = 0; gin < n_g_; gin++)
90 ✔
256
        chi_delayed.slice(a, d, gin) = temp_chi_d.slice(a, d);
180 ✔
257

258
  // Get prompt and delayed nu-fission directly
259
  read_nd_tensor(xsdata_grp, "prompt-nu-fission", prompt_nu_fission, true);
15 ✔
260
  read_nd_tensor(xsdata_grp, "delayed-nu-fission", delayed_nu_fission, true);
15 ✔
261
}
30 ✔
262

263
void XsData::fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
1,151 ✔
264
{
265
  // No beta is provided and there is no prompt/delay distinction.
266
  // Therefore, the code only considers the data as prompt.
267

268
  // Get chi
269
  tensor::Tensor<double> temp_chi = tensor::zeros<double>({n_ang, n_g_});
1,151 ✔
270
  read_nd_tensor(xsdata_grp, "chi", temp_chi, true);
1,151 ✔
271

272
  // Normalize chi so it sums to 1 over outgoing groups for each angle
273
  for (size_t a = 0; a < n_ang; a++) {
2,392 ✔
274
    tensor::View<double> row = temp_chi.slice(a);
1,241 ✔
275
    row /= row.sum();
1,241 ✔
276
  }
1,241 ✔
277

278
  // Replicate the energy spectrum across all incoming groups
279
  for (size_t a = 0; a < n_ang; a++)
2,392 ✔
280
    for (size_t gin = 0; gin < n_g_; gin++)
7,198 ✔
281
      chi_prompt.slice(a, gin) = temp_chi.slice(a);
17,871 ✔
282

283
  // Get nu-fission directly
284
  read_nd_tensor(xsdata_grp, "nu-fission", prompt_nu_fission, true);
1,151 ✔
285
}
1,151 ✔
286

287
//==============================================================================
288

289
void XsData::fission_matrix_beta_from_hdf5(
30 ✔
290
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
291
{
292
  // Data is provided as nu-fission and chi with a beta for delayed info
293

294
  // Get nu-fission matrix
295
  tensor::Tensor<double> temp_matrix =
30 ✔
296
    tensor::zeros<double>({n_ang, n_g_, n_g_});
30 ✔
297
  read_nd_tensor(xsdata_grp, "nu-fission", temp_matrix, true);
30 ✔
298

299
  // Get beta (strategy will depend upon the number of dimensions in beta)
300
  hid_t beta_dset = open_dataset(xsdata_grp, "beta");
30 ✔
301
  int beta_ndims = dataset_ndims(beta_dset);
30 ✔
302
  close_dataset(beta_dset);
30 ✔
303
  int ndim_target = 1;
30 ✔
304
  if (!is_isotropic)
30 !
UNCOV
305
    ndim_target += 2;
×
306
  if (beta_ndims == ndim_target) {
30 ✔
307
    tensor::Tensor<double> temp_beta = tensor::zeros<double>({n_ang, n_dg_});
15 ✔
308
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
15 ✔
309

310
    auto beta_sum = temp_beta.sum(1);
15 ✔
311
    auto matrix_gout_sum = temp_matrix.sum(2);
15 ✔
312

313
    // prompt_nu_fission = sum_gout(matrix) * (1 - beta_total)
314
    for (size_t a = 0; a < n_ang; a++)
30 ✔
315
      for (size_t g = 0; g < n_g_; g++)
45 ✔
316
        prompt_nu_fission(a, g) = matrix_gout_sum(a, g) * (1.0 - beta_sum(a));
30 ✔
317

318
    // chi_prompt = (1 - beta_total) * nu-fission matrix (unnormalized)
319
    for (size_t a = 0; a < n_ang; a++)
30 ✔
320
      for (size_t gin = 0; gin < n_g_; gin++)
45 ✔
321
        for (size_t gout = 0; gout < n_g_; gout++)
90 ✔
322
          chi_prompt(a, gin, gout) =
60 ✔
323
            (1.0 - beta_sum(a)) * temp_matrix(a, gin, gout);
60 ✔
324

325
    // Delayed nu-fission is the outer product of the delayed neutron
326
    // fraction (beta) and the total fission rate summed over outgoing groups
327
    for (size_t a = 0; a < n_ang; a++)
30 ✔
328
      for (size_t d = 0; d < n_dg_; d++)
45 ✔
329
        for (size_t g = 0; g < n_g_; g++)
90 ✔
330
          delayed_nu_fission(a, d, g) = temp_beta(a, d) * matrix_gout_sum(a, g);
60 ✔
331

332
    // chi_delayed = beta * nu-fission matrix, expanded across delayed groups
333
    for (size_t a = 0; a < n_ang; a++)
30 ✔
334
      for (size_t d = 0; d < n_dg_; d++)
45 ✔
335
        for (size_t gin = 0; gin < n_g_; gin++)
90 ✔
336
          for (size_t gout = 0; gout < n_g_; gout++)
180 ✔
337
            chi_delayed(a, d, gin, gout) =
120 ✔
338
              temp_beta(a, d) * temp_matrix(a, gin, gout);
120 ✔
339

340
  } else if (beta_ndims == ndim_target + 1) {
60 !
341
    tensor::Tensor<double> temp_beta =
15 ✔
342
      tensor::zeros<double>({n_ang, n_dg_, n_g_});
15 ✔
343
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
15 ✔
344

345
    auto beta_sum = temp_beta.sum(1);
15 ✔
346
    auto matrix_gout_sum = temp_matrix.sum(2);
15 ✔
347

348
    // prompt_nu_fission = sum_gout(matrix) * (1 - beta_total)
349
    // Here beta is energy-dependent, so beta_sum is 2D [n_ang, n_g]
350
    for (size_t a = 0; a < n_ang; a++)
30 ✔
351
      for (size_t g = 0; g < n_g_; g++)
45 ✔
352
        prompt_nu_fission(a, g) =
30 ✔
353
          matrix_gout_sum(a, g) * (1.0 - beta_sum(a, g));
30 ✔
354

355
    // chi_prompt = (1 - beta_sum) * nu-fission matrix (unnormalized)
356
    for (size_t a = 0; a < n_ang; a++)
30 ✔
357
      for (size_t gin = 0; gin < n_g_; gin++)
45 ✔
358
        for (size_t gout = 0; gout < n_g_; gout++)
90 ✔
359
          chi_prompt(a, gin, gout) =
60 ✔
360
            (1.0 - beta_sum(a, gin)) * temp_matrix(a, gin, gout);
60 ✔
361

362
    // Delayed nu-fission: beta is energy-dependent [n_ang, n_dg, n_g],
363
    // scale by total fission rate summed over outgoing groups
364
    for (size_t a = 0; a < n_ang; a++)
30 ✔
365
      for (size_t d = 0; d < n_dg_; d++)
45 ✔
366
        for (size_t g = 0; g < n_g_; g++)
90 ✔
367
          delayed_nu_fission(a, d, g) =
60 ✔
368
            temp_beta(a, d, g) * matrix_gout_sum(a, g);
60 ✔
369

370
    // chi_delayed = beta * nu-fission matrix, expanded across delayed groups
371
    for (size_t a = 0; a < n_ang; a++)
30 ✔
372
      for (size_t d = 0; d < n_dg_; d++)
45 ✔
373
        for (size_t gin = 0; gin < n_g_; gin++)
90 ✔
374
          for (size_t gout = 0; gout < n_g_; gout++)
180 ✔
375
            chi_delayed(a, d, gin, gout) =
120 ✔
376
              temp_beta(a, d, gin) * temp_matrix(a, gin, gout);
120 ✔
377
  }
45 ✔
378

379
  // Normalize chi_prompt so it sums to 1 over outgoing groups
380
  for (size_t a = 0; a < n_ang; a++)
60 ✔
381
    for (size_t gin = 0; gin < n_g_; gin++) {
90 ✔
382
      tensor::View<double> row = chi_prompt.slice(a, gin);
60 ✔
383
      row /= row.sum();
60 ✔
384
    }
60 ✔
385

386
  // Normalize chi_delayed so it sums to 1 over outgoing groups
387
  for (size_t a = 0; a < n_ang; a++)
60 ✔
388
    for (size_t d = 0; d < n_dg_; d++)
90 ✔
389
      for (size_t gin = 0; gin < n_g_; gin++) {
180 ✔
390
        tensor::View<double> row = chi_delayed.slice(a, d, gin);
120 ✔
391
        row /= row.sum();
120 ✔
392
      }
120 ✔
393
}
30 ✔
394

395
void XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
15 ✔
396
{
397
  // Data is provided separately as prompt + delayed nu-fission and chi
398

399
  // Get the prompt nu-fission matrix
400
  tensor::Tensor<double> temp_matrix_p =
15 ✔
401
    tensor::zeros<double>({n_ang, n_g_, n_g_});
15 ✔
402
  read_nd_tensor(xsdata_grp, "prompt-nu-fission", temp_matrix_p, true);
15 ✔
403

404
  // prompt_nu_fission is the sum over outgoing groups
405
  prompt_nu_fission = temp_matrix_p.sum(2);
15 ✔
406

407
  // chi_prompt is the nu-fission matrix normalized over outgoing groups
408
  for (size_t a = 0; a < n_ang; a++)
30 ✔
409
    for (size_t gin = 0; gin < n_g_; gin++)
45 ✔
410
      for (size_t gout = 0; gout < n_g_; gout++)
90 ✔
411
        chi_prompt(a, gin, gout) =
60 ✔
412
          temp_matrix_p(a, gin, gout) / prompt_nu_fission(a, gin);
60 ✔
413

414
  // Get the delayed nu-fission matrix
415
  tensor::Tensor<double> temp_matrix_d =
15 ✔
416
    tensor::zeros<double>({n_ang, n_dg_, n_g_, n_g_});
15 ✔
417
  read_nd_tensor(xsdata_grp, "delayed-nu-fission", temp_matrix_d, true);
15 ✔
418

419
  // delayed_nu_fission is the sum over outgoing groups
420
  delayed_nu_fission = temp_matrix_d.sum(3);
15 ✔
421

422
  // chi_delayed is the delayed nu-fission matrix normalized over outgoing
423
  // groups
424
  for (size_t a = 0; a < n_ang; a++)
30 ✔
425
    for (size_t d = 0; d < n_dg_; d++)
45 ✔
426
      for (size_t gin = 0; gin < n_g_; gin++)
90 ✔
427
        for (size_t gout = 0; gout < n_g_; gout++)
180 ✔
428
          chi_delayed(a, d, gin, gout) =
120 ✔
429
            temp_matrix_d(a, d, gin, gout) / delayed_nu_fission(a, d, gin);
120 ✔
430
}
30 ✔
431

432
void XsData::fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
75 ✔
433
{
434
  // No beta is provided and there is no prompt/delay distinction.
435
  // Therefore, the code only considers the data as prompt.
436

437
  // Get nu-fission matrix
438
  tensor::Tensor<double> temp_matrix =
75 ✔
439
    tensor::zeros<double>({n_ang, n_g_, n_g_});
75 ✔
440
  read_nd_tensor(xsdata_grp, "nu-fission", temp_matrix, true);
75 ✔
441

442
  // prompt_nu_fission is the sum over outgoing groups
443
  prompt_nu_fission = temp_matrix.sum(2);
75 ✔
444

445
  // chi_prompt is the nu-fission matrix normalized over outgoing groups
446
  for (size_t a = 0; a < n_ang; a++)
150 ✔
447
    for (size_t gin = 0; gin < n_g_; gin++)
225 ✔
448
      for (size_t gout = 0; gout < n_g_; gout++)
450 ✔
449
        chi_prompt(a, gin, gout) =
300 ✔
450
          temp_matrix(a, gin, gout) / prompt_nu_fission(a, gin);
300 ✔
451
}
75 ✔
452

453
//==============================================================================
454

455
void XsData::fission_from_hdf5(
1,331 ✔
456
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
457
{
458
  // Get the fission and kappa_fission data xs; these are optional
459
  read_nd_tensor(xsdata_grp, "fission", fission);
1,331 ✔
460
  read_nd_tensor(xsdata_grp, "kappa-fission", kappa_fission);
1,331 ✔
461

462
  // Get the data; the strategy for doing so depends on if the data is provided
463
  // as a nu-fission matrix or a set of chi and nu-fission vectors
464
  if (object_exists(xsdata_grp, "chi") ||
1,466 ✔
465
      object_exists(xsdata_grp, "chi-prompt")) {
135 ✔
466
    if (n_dg_ == 0) {
1,211 ✔
467
      fission_vector_no_delayed_from_hdf5(xsdata_grp, n_ang);
1,151 ✔
468
    } else {
469
      if (object_exists(xsdata_grp, "beta")) {
60 ✔
470
        fission_vector_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
45 ✔
471
      } else {
472
        fission_vector_no_beta_from_hdf5(xsdata_grp, n_ang);
15 ✔
473
      }
474
    }
475
  } else {
476
    if (n_dg_ == 0) {
120 ✔
477
      fission_matrix_no_delayed_from_hdf5(xsdata_grp, n_ang);
75 ✔
478
    } else {
479
      if (object_exists(xsdata_grp, "beta")) {
45 ✔
480
        fission_matrix_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
30 ✔
481
      } else {
482
        fission_matrix_no_beta_from_hdf5(xsdata_grp, n_ang);
15 ✔
483
      }
484
    }
485
  }
486

487
  // Combine prompt_nu_fission and delayed_nu_fission into nu_fission
488
  if (n_dg_ == 0) {
1,331 ✔
489
    nu_fission = prompt_nu_fission;
1,226 ✔
490
  } else {
491
    nu_fission = prompt_nu_fission + delayed_nu_fission.sum(1);
315 ✔
492
  }
493
}
1,331 ✔
494

495
//==============================================================================
496

497
void XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang,
4,268 ✔
498
  AngleDistributionType scatter_format,
499
  AngleDistributionType final_scatter_format, int order_data)
500
{
501
  if (!object_exists(xsdata_grp, "scatter_data")) {
4,268 !
UNCOV
502
    fatal_error("Must provide scatter_data group!");
×
503
  }
504
  hid_t scatt_grp = open_group(xsdata_grp, "scatter_data");
4,268 ✔
505

506
  // Get the outgoing group boundary indices
507
  tensor::Tensor<int> gmin = tensor::zeros<int>({n_ang, n_g_});
4,268 ✔
508
  read_nd_tensor(scatt_grp, "g_min", gmin, true);
4,268 ✔
509
  tensor::Tensor<int> gmax = tensor::zeros<int>({n_ang, n_g_});
4,268 ✔
510
  read_nd_tensor(scatt_grp, "g_max", gmax, true);
4,268 ✔
511

512
  // Make gmin and gmax start from 0 vice 1 as they do in the library
513
  gmin -= 1;
4,268 ✔
514
  gmax -= 1;
4,268 ✔
515

516
  // Now use this info to find the length of a vector to hold the flattened
517
  // data.
518
  size_t length = order_data * (gmax - gmin + 1).sum();
8,536 ✔
519

520
  double_4dvec input_scatt(n_ang, double_3dvec(n_g_));
6,309 ✔
521
  tensor::Tensor<double> temp_arr = tensor::zeros<double>({length});
4,268 ✔
522
  read_nd_tensor(scatt_grp, "scatter_matrix", temp_arr, true);
4,268 ✔
523

524
  // Compare the number of orders given with the max order of the problem;
525
  // strip off the superfluous orders if needed
526
  int order_dim;
4,268 ✔
527
  if (scatter_format == AngleDistributionType::LEGENDRE) {
4,268 ✔
528
    order_dim = std::min(order_data - 1, settings::max_order) + 1;
4,163 ✔
529
  } else {
530
    order_dim = order_data;
531
  }
532

533
  // convert the flattened temp_arr to a jagged array for passing to
534
  // scatt data
535
  size_t temp_idx = 0;
4,268 ✔
536
  for (size_t a = 0; a < n_ang; a++) {
8,626 ✔
537
    for (size_t gin = 0; gin < n_g_; gin++) {
20,675 ✔
538
      input_scatt[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
16,317 ✔
539
      for (size_t i_gout = 0; i_gout < input_scatt[a][gin].size(); i_gout++) {
98,909 ✔
540
        input_scatt[a][gin][i_gout].resize(order_dim);
82,592 ✔
541
        for (size_t l = 0; l < order_dim; l++) {
175,903 ✔
542
          input_scatt[a][gin][i_gout][l] = temp_arr[temp_idx++];
93,311 ✔
543
        }
544
        // Adjust index for the orders we didnt take
545
        temp_idx += (order_data - order_dim);
82,592 ✔
546
      }
547
    }
548
  }
549

550
  // Get multiplication matrix
551
  double_3dvec temp_mult(n_ang, double_2dvec(n_g_));
6,309 ✔
552
  if (object_exists(scatt_grp, "multiplicity_matrix")) {
4,268 ✔
553
    temp_arr.resize({length / order_data});
871 ✔
554
    read_nd_tensor(scatt_grp, "multiplicity_matrix", temp_arr);
871 ✔
555

556
    // convert the flat temp_arr to a jagged array for passing to scatt data
557
    size_t temp_idx = 0;
558
    for (size_t a = 0; a < n_ang; a++) {
1,742 ✔
559
      for (size_t gin = 0; gin < n_g_; gin++) {
9,984 ✔
560
        temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
9,113 ✔
561
        for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
76,694 ✔
562
          temp_mult[a][gin][i_gout] = temp_arr[temp_idx++];
67,581 ✔
563
        }
564
      }
565
    }
566
  } else {
567
    // Use a default: multiplicities are 1.0.
568
    for (size_t a = 0; a < n_ang; a++) {
6,884 ✔
569
      for (size_t gin = 0; gin < n_g_; gin++) {
10,691 ✔
570
        temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
7,204 ✔
571
        for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
22,215 ✔
572
          temp_mult[a][gin][i_gout] = 1.;
15,011 ✔
573
        }
574
      }
575
    }
576
  }
577
  close_group(scatt_grp);
4,268 ✔
578

579
  // Finally, convert the Legendre data to tabular, if needed
580
  if (scatter_format == AngleDistributionType::LEGENDRE &&
4,268 ✔
581
      final_scatter_format == AngleDistributionType::TABULAR) {
4,268 ✔
582
    for (size_t a = 0; a < n_ang; a++) {
7,891 ✔
583
      ScattDataLegendre legendre_scatt;
3,968 ✔
584
      tensor::Tensor<int> in_gmin(gmin.slice(a));
3,968 ✔
585
      tensor::Tensor<int> in_gmax(gmax.slice(a));
3,968 ✔
586

587
      legendre_scatt.init(in_gmin, in_gmax, temp_mult[a], input_scatt[a]);
3,968 ✔
588

589
      // Now create a tabular version of legendre_scatt
590
      convert_legendre_to_tabular(
3,968 ✔
591
        legendre_scatt, *static_cast<ScattDataTabular*>(scatter[a].get()));
3,968 ✔
592

593
      scatter_format = final_scatter_format;
3,968 ✔
594
    }
7,936 ✔
595
  } else {
596
    // We are sticking with the current representation
597
    // Initialize the ScattData object with this data
598
    for (size_t a = 0; a < n_ang; a++) {
735 ✔
599
      tensor::Tensor<int> in_gmin(gmin.slice(a));
390 ✔
600
      tensor::Tensor<int> in_gmax(gmax.slice(a));
390 ✔
601
      scatter[a]->init(in_gmin, in_gmax, temp_mult[a], input_scatt[a]);
390 ✔
602
    }
780 ✔
603
  }
604
}
17,072 ✔
605

606
//==============================================================================
607

608
void XsData::combine(
3,743 ✔
609
  const vector<XsData*>& those_xs, const vector<double>& scalars)
610
{
611
  // Combine the non-scattering data
612
  for (size_t i = 0; i < those_xs.size(); i++) {
8,026 ✔
613
    XsData* that = those_xs[i];
4,283 ✔
614
    if (!equiv(*that))
4,283 !
UNCOV
615
      fatal_error("Cannot combine the XsData objects!");
×
616
    double scalar = scalars[i];
4,283 ✔
617
    total += scalar * that->total;
4,283 ✔
618
    absorption += scalar * that->absorption;
4,283 ✔
619
    if (i == 0) {
4,283 ✔
620
      inverse_velocity = that->inverse_velocity;
3,743 ✔
621
    }
622
    if (!that->prompt_nu_fission.empty()) {
4,283 ✔
623
      nu_fission += scalar * that->nu_fission;
1,346 ✔
624
      prompt_nu_fission += scalar * that->prompt_nu_fission;
1,346 ✔
625
      kappa_fission += scalar * that->kappa_fission;
1,346 ✔
626
      fission += scalar * that->fission;
1,346 ✔
627
      delayed_nu_fission += scalar * that->delayed_nu_fission;
1,346 ✔
628
      // Accumulate chi_prompt weighted by total prompt nu-fission
629
      // (summed over energy groups) for this constituent
630
      {
1,346 ✔
631
        auto pnf_sum = that->prompt_nu_fission.sum(1);
1,346 ✔
632
        size_t n_ang = chi_prompt.shape(0);
1,346 !
633
        size_t n_g = chi_prompt.shape(1);
1,346 !
634
        for (size_t a = 0; a < n_ang; a++)
2,782 ✔
635
          for (size_t gin = 0; gin < n_g; gin++)
7,783 ✔
636
            for (size_t gout = 0; gout < n_g; gout++)
172,426 ✔
637
              chi_prompt(a, gin, gout) +=
166,079 ✔
638
                scalar * pnf_sum(a) * that->chi_prompt(a, gin, gout);
166,079 ✔
639
      }
1,346 ✔
640
      // Accumulate chi_delayed weighted by total delayed nu-fission
641
      // (summed over energy groups) for this constituent
642
      {
1,346 ✔
643
        auto dnf_sum = that->delayed_nu_fission.sum(2);
1,346 ✔
644
        size_t n_ang = chi_delayed.shape(0);
1,346 !
645
        size_t n_dg = chi_delayed.shape(1);
1,346 !
646
        size_t n_g = chi_delayed.shape(2);
1,346 !
647
        for (size_t a = 0; a < n_ang; a++)
2,782 ✔
648
          for (size_t d = 0; d < n_dg; d++)
1,706 ✔
649
            for (size_t gin = 0; gin < n_g; gin++)
810 ✔
650
              for (size_t gout = 0; gout < n_g; gout++)
1,620 ✔
651
                chi_delayed(a, d, gin, gout) +=
1,080 ✔
652
                  scalar * dnf_sum(a, d) * that->chi_delayed(a, d, gin, gout);
1,080 ✔
653
      }
1,346 ✔
654
    }
655
    decay_rate += scalar * that->decay_rate;
8,566 ✔
656
  }
657

658
  // Normalize chi_prompt so it sums to 1 over outgoing groups
659
  {
3,743 ✔
660
    size_t n_ang = chi_prompt.shape(0);
3,743 ✔
661
    size_t n_g = chi_prompt.shape(1);
3,743 ✔
662
    for (size_t a = 0; a < n_ang; a++)
4,999 ✔
663
      for (size_t gin = 0; gin < n_g; gin++) {
7,243 ✔
664
        tensor::View<double> row = chi_prompt.slice(a, gin);
5,987 ✔
665
        row /= row.sum();
5,987 ✔
666
      }
5,987 ✔
667
  }
668
  // Normalize chi_delayed so it sums to 1 over outgoing groups
669
  {
3,743 ✔
670
    size_t n_ang = chi_delayed.shape(0);
3,743 ✔
671
    size_t n_dg = chi_delayed.shape(1);
3,743 ✔
672
    size_t n_g = chi_delayed.shape(2);
3,743 ✔
673
    for (size_t a = 0; a < n_ang; a++)
4,999 ✔
674
      for (size_t d = 0; d < n_dg; d++)
1,526 ✔
675
        for (size_t gin = 0; gin < n_g; gin++) {
810 ✔
676
          tensor::View<double> row = chi_delayed.slice(a, d, gin);
540 ✔
677
          row /= row.sum();
540 ✔
678
        }
540 ✔
679
  }
680

681
  // Allow the ScattData object to combine itself
682
  for (size_t a = 0; a < total.shape(0); a++) {
15,152 !
683
    // Build vector of the scattering objects to incorporate
684
    vector<ScattData*> those_scatts(those_xs.size());
3,833 ✔
685
    for (size_t i = 0; i < those_xs.size(); i++) {
8,206 ✔
686
      those_scatts[i] = those_xs[i]->scatter[a].get();
4,373 ✔
687
    }
688

689
    // Now combine these guys
690
    scatter[a]->combine(those_scatts, scalars);
3,833 ✔
691
  }
3,833 ✔
692
}
3,743 ✔
693

694
//==============================================================================
695

696
bool XsData::equiv(const XsData& that)
4,283 ✔
697
{
698
  return (absorption.shape() == that.absorption.shape());
4,283 ✔
699
}
700

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

© 2026 Coveralls, Inc