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

openmc-dev / openmc / 23084721708

14 Mar 2026 08:56AM UTC coverage: 81.599% (-0.5%) from 82.058%
23084721708

Pull #2693

github

web-flow
Merge 0ed23ee59 into bc9c31e0f
Pull Request #2693: Add reactivity control to coupled transport-depletion analyses

17575 of 25275 branches covered (69.54%)

Branch coverage included in aggregate %.

74 of 85 new or added lines in 4 files covered. (87.06%)

3755 existing lines in 99 files now uncovered.

58074 of 67433 relevant lines covered (86.12%)

47252067.37 hits per line

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

96.02
/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,
6,207✔
24
  int n_pol, int n_azi, size_t n_groups, size_t n_d_groups)
6,207✔
25
  : n_g_(n_groups), n_dg_(n_d_groups)
6,207✔
26
{
27
  size_t n_ang = n_pol * n_azi;
6,207✔
28

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

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

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

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

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

65
  for (int a = 0; a < n_ang; a++) {
12,594✔
66
    if (scatter_format == AngleDistributionType::HISTOGRAM) {
6,387✔
67
      scatter.emplace_back(new ScattDataHistogram);
120✔
68
    } else if (scatter_format == AngleDistributionType::TABULAR) {
6,267✔
69
      scatter.emplace_back(new ScattDataTabular);
5,757✔
70
    } else if (scatter_format == AngleDistributionType::LEGENDRE) {
510✔
71
      scatter.emplace_back(new ScattDataLegendre);
510✔
72
    }
73
  }
74
}
6,207✔
75

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

78
void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable,
3,366✔
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;
3,366✔
85
  size_t energy_groups = total.shape(1);
3,366!
86

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

96
  // Get scattering data
97
  scatter_from_hdf5(
3,366✔
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++)
15,115✔
103
    if (absorption.data()[i] == 0.0)
11,749!
UNCOV
104
      absorption.data()[i] = 1.e-10;
×
105

106
  // Get or calculate the total x/s
107
  if (object_exists(xsdata_grp, "total")) {
3,366!
108
    read_nd_tensor(xsdata_grp, "total", total);
3,366✔
109
  } else {
UNCOV
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++)
15,115✔
120
    if (total.data()[i] == 0.0)
11,749!
UNCOV
121
      total.data()[i] = 1.e-10;
×
122
}
3,366✔
123

124
//==============================================================================
125

126
void XsData::fission_vector_beta_from_hdf5(
45✔
127
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
128
{
129
  // Data is provided as nu-fission and chi with a beta for delayed info
130

131
  // Get chi
132
  tensor::Tensor<double> temp_chi = tensor::zeros<double>({n_ang, n_g_});
45✔
133
  read_nd_tensor(xsdata_grp, "chi", temp_chi, true);
45✔
134

135
  // Normalize chi so it sums to 1 over outgoing groups for each angle
136
  for (size_t a = 0; a < n_ang; a++) {
90✔
137
    tensor::View<double> row = temp_chi.slice(a);
45✔
138
    row /= row.sum();
45✔
139
  }
45✔
140

141
  // Replicate the energy spectrum across all incoming groups — the
142
  // spectrum is independent of the incoming neutron energy
143
  for (size_t a = 0; a < n_ang; a++)
90✔
144
    for (size_t gin = 0; gin < n_g_; gin++)
135✔
145
      chi_prompt.slice(a, gin) = temp_chi.slice(a);
270✔
146

147
  // Same spectrum for delayed neutrons, replicated across delayed groups
148
  for (size_t a = 0; a < n_ang; a++)
90✔
149
    for (size_t d = 0; d < n_dg_; d++)
195✔
150
      for (size_t gin = 0; gin < n_g_; gin++)
450✔
151
        chi_delayed.slice(a, d, gin) = temp_chi.slice(a);
900✔
152

153
  // Get nu-fission
154
  tensor::Tensor<double> temp_nufiss = tensor::zeros<double>({n_ang, n_g_});
45✔
155
  read_nd_tensor(xsdata_grp, "nu-fission", temp_nufiss, true);
45✔
156

157
  // Get beta (strategy will depend upon the number of dimensions in beta)
158
  hid_t beta_dset = open_dataset(xsdata_grp, "beta");
45✔
159
  int beta_ndims = dataset_ndims(beta_dset);
45✔
160
  close_dataset(beta_dset);
45✔
161
  int ndim_target = 1;
45✔
162
  if (!is_isotropic)
45!
UNCOV
163
    ndim_target += 2;
×
164
  if (beta_ndims == ndim_target) {
45✔
165
    tensor::Tensor<double> temp_beta = tensor::zeros<double>({n_ang, n_dg_});
30✔
166
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
30✔
167

168
    // prompt_nu_fission = (1 - sum_of_beta) * nu_fission
169
    auto beta_sum = temp_beta.sum(1);
30✔
170
    for (size_t a = 0; a < n_ang; a++)
60✔
171
      for (size_t g = 0; g < n_g_; g++)
90✔
172
        prompt_nu_fission(a, g) = temp_nufiss(a, g) * (1.0 - beta_sum(a));
60✔
173

174
    // Delayed nu-fission is the outer product of the delayed neutron
175
    // fraction (beta) and the fission production rate (nu-fission)
176
    for (size_t a = 0; a < n_ang; a++)
60✔
177
      for (size_t d = 0; d < n_dg_; d++)
150✔
178
        for (size_t g = 0; g < n_g_; g++)
360✔
179
          delayed_nu_fission(a, d, g) = temp_beta(a, d) * temp_nufiss(a, g);
240✔
180
  } else if (beta_ndims == ndim_target + 1) {
75!
181
    tensor::Tensor<double> temp_beta =
15✔
182
      tensor::zeros<double>({n_ang, n_dg_, n_g_});
15✔
183
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
15✔
184

185
    // prompt_nu_fission = (1 - sum_of_beta) * nu_fission
186
    // Here beta is energy-dependent, so sum over delayed groups (axis 1)
187
    auto beta_sum = temp_beta.sum(1);
15✔
188
    for (size_t a = 0; a < n_ang; a++)
30✔
189
      for (size_t g = 0; g < n_g_; g++)
45✔
190
        prompt_nu_fission(a, g) = temp_nufiss(a, g) * (1.0 - beta_sum(a, g));
30✔
191

192
    // Delayed nu-fission: beta is already energy-dependent [n_ang, n_dg, n_g],
193
    // so scale each delayed group's beta by the total nu-fission for that group
194
    for (size_t a = 0; a < n_ang; a++)
30✔
195
      for (size_t d = 0; d < n_dg_; d++)
45✔
196
        for (size_t g = 0; g < n_g_; g++)
90✔
197
          delayed_nu_fission(a, d, g) = temp_beta(a, d, g) * temp_nufiss(a, g);
60✔
198
  }
30✔
199
}
90✔
200

201
void XsData::fission_vector_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
15✔
202
{
203
  // Data is provided separately as prompt + delayed nu-fission and chi
204

205
  // Get chi-prompt
206
  tensor::Tensor<double> temp_chi_p = tensor::zeros<double>({n_ang, n_g_});
15✔
207
  read_nd_tensor(xsdata_grp, "chi-prompt", temp_chi_p, true);
15✔
208

209
  // Normalize prompt chi so it sums to 1 over outgoing groups for each angle
210
  for (size_t a = 0; a < n_ang; a++) {
30✔
211
    tensor::View<double> row = temp_chi_p.slice(a);
15✔
212
    row /= row.sum();
15✔
213
  }
15✔
214

215
  // Get chi-delayed
216
  tensor::Tensor<double> temp_chi_d =
15✔
217
    tensor::zeros<double>({n_ang, n_dg_, n_g_});
15✔
218
  read_nd_tensor(xsdata_grp, "chi-delayed", temp_chi_d, true);
15✔
219

220
  // Normalize delayed chi so it sums to 1 over outgoing groups for each
221
  // angle and delayed group
222
  for (size_t a = 0; a < n_ang; a++)
30✔
223
    for (size_t d = 0; d < n_dg_; d++) {
45✔
224
      tensor::View<double> row = temp_chi_d.slice(a, d);
30✔
225
      row /= row.sum();
30✔
226
    }
30✔
227

228
  // Replicate the prompt spectrum across all incoming groups
229
  for (size_t a = 0; a < n_ang; a++)
30✔
230
    for (size_t gin = 0; gin < n_g_; gin++)
45✔
231
      chi_prompt.slice(a, gin) = temp_chi_p.slice(a);
90✔
232

233
  // Replicate the delayed spectrum across all incoming groups
234
  for (size_t a = 0; a < n_ang; a++)
30✔
235
    for (size_t d = 0; d < n_dg_; d++)
45✔
236
      for (size_t gin = 0; gin < n_g_; gin++)
90✔
237
        chi_delayed.slice(a, d, gin) = temp_chi_d.slice(a, d);
180✔
238

239
  // Get prompt and delayed nu-fission directly
240
  read_nd_tensor(xsdata_grp, "prompt-nu-fission", prompt_nu_fission, true);
15✔
241
  read_nd_tensor(xsdata_grp, "delayed-nu-fission", delayed_nu_fission, true);
15✔
242
}
30✔
243

244
void XsData::fission_vector_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
1,061✔
245
{
246
  // No beta is provided and there is no prompt/delay distinction.
247
  // Therefore, the code only considers the data as prompt.
248

249
  // Get chi
250
  tensor::Tensor<double> temp_chi = tensor::zeros<double>({n_ang, n_g_});
1,061✔
251
  read_nd_tensor(xsdata_grp, "chi", temp_chi, true);
1,061✔
252

253
  // Normalize chi so it sums to 1 over outgoing groups for each angle
254
  for (size_t a = 0; a < n_ang; a++) {
2,212✔
255
    tensor::View<double> row = temp_chi.slice(a);
1,151✔
256
    row /= row.sum();
1,151✔
257
  }
1,151✔
258

259
  // Replicate the energy spectrum across all incoming groups
260
  for (size_t a = 0; a < n_ang; a++)
2,212✔
261
    for (size_t gin = 0; gin < n_g_; gin++)
5,833✔
262
      chi_prompt.slice(a, gin) = temp_chi.slice(a);
14,046✔
263

264
  // Get nu-fission directly
265
  read_nd_tensor(xsdata_grp, "nu-fission", prompt_nu_fission, true);
1,061✔
266
}
1,061✔
267

268
//==============================================================================
269

270
void XsData::fission_matrix_beta_from_hdf5(
30✔
271
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
272
{
273
  // Data is provided as nu-fission and chi with a beta for delayed info
274

275
  // Get nu-fission matrix
276
  tensor::Tensor<double> temp_matrix =
30✔
277
    tensor::zeros<double>({n_ang, n_g_, n_g_});
30✔
278
  read_nd_tensor(xsdata_grp, "nu-fission", temp_matrix, true);
30✔
279

280
  // Get beta (strategy will depend upon the number of dimensions in beta)
281
  hid_t beta_dset = open_dataset(xsdata_grp, "beta");
30✔
282
  int beta_ndims = dataset_ndims(beta_dset);
30✔
283
  close_dataset(beta_dset);
30✔
284
  int ndim_target = 1;
30✔
285
  if (!is_isotropic)
30!
UNCOV
286
    ndim_target += 2;
×
287
  if (beta_ndims == ndim_target) {
30✔
288
    tensor::Tensor<double> temp_beta = tensor::zeros<double>({n_ang, n_dg_});
15✔
289
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
15✔
290

291
    auto beta_sum = temp_beta.sum(1);
15✔
292
    auto matrix_gout_sum = temp_matrix.sum(2);
15✔
293

294
    // prompt_nu_fission = sum_gout(matrix) * (1 - beta_total)
295
    for (size_t a = 0; a < n_ang; a++)
30✔
296
      for (size_t g = 0; g < n_g_; g++)
45✔
297
        prompt_nu_fission(a, g) = matrix_gout_sum(a, g) * (1.0 - beta_sum(a));
30✔
298

299
    // chi_prompt = (1 - beta_total) * nu-fission matrix (unnormalized)
300
    for (size_t a = 0; a < n_ang; a++)
30✔
301
      for (size_t gin = 0; gin < n_g_; gin++)
45✔
302
        for (size_t gout = 0; gout < n_g_; gout++)
90✔
303
          chi_prompt(a, gin, gout) =
60✔
304
            (1.0 - beta_sum(a)) * temp_matrix(a, gin, gout);
60✔
305

306
    // Delayed nu-fission is the outer product of the delayed neutron
307
    // fraction (beta) and the total fission rate summed over outgoing groups
308
    for (size_t a = 0; a < n_ang; a++)
30✔
309
      for (size_t d = 0; d < n_dg_; d++)
45✔
310
        for (size_t g = 0; g < n_g_; g++)
90✔
311
          delayed_nu_fission(a, d, g) = temp_beta(a, d) * matrix_gout_sum(a, g);
60✔
312

313
    // chi_delayed = beta * nu-fission matrix, expanded across delayed groups
314
    for (size_t a = 0; a < n_ang; a++)
30✔
315
      for (size_t d = 0; d < n_dg_; d++)
45✔
316
        for (size_t gin = 0; gin < n_g_; gin++)
90✔
317
          for (size_t gout = 0; gout < n_g_; gout++)
180✔
318
            chi_delayed(a, d, gin, gout) =
120✔
319
              temp_beta(a, d) * temp_matrix(a, gin, gout);
120✔
320

321
  } else if (beta_ndims == ndim_target + 1) {
60!
322
    tensor::Tensor<double> temp_beta =
15✔
323
      tensor::zeros<double>({n_ang, n_dg_, n_g_});
15✔
324
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
15✔
325

326
    auto beta_sum = temp_beta.sum(1);
15✔
327
    auto matrix_gout_sum = temp_matrix.sum(2);
15✔
328

329
    // prompt_nu_fission = sum_gout(matrix) * (1 - beta_total)
330
    // Here beta is energy-dependent, so beta_sum is 2D [n_ang, n_g]
331
    for (size_t a = 0; a < n_ang; a++)
30✔
332
      for (size_t g = 0; g < n_g_; g++)
45✔
333
        prompt_nu_fission(a, g) =
30✔
334
          matrix_gout_sum(a, g) * (1.0 - beta_sum(a, g));
30✔
335

336
    // chi_prompt = (1 - beta_sum) * nu-fission matrix (unnormalized)
337
    for (size_t a = 0; a < n_ang; a++)
30✔
338
      for (size_t gin = 0; gin < n_g_; gin++)
45✔
339
        for (size_t gout = 0; gout < n_g_; gout++)
90✔
340
          chi_prompt(a, gin, gout) =
60✔
341
            (1.0 - beta_sum(a, gin)) * temp_matrix(a, gin, gout);
60✔
342

343
    // Delayed nu-fission: beta is energy-dependent [n_ang, n_dg, n_g],
344
    // scale by total fission rate summed over outgoing groups
345
    for (size_t a = 0; a < n_ang; a++)
30✔
346
      for (size_t d = 0; d < n_dg_; d++)
45✔
347
        for (size_t g = 0; g < n_g_; g++)
90✔
348
          delayed_nu_fission(a, d, g) =
60✔
349
            temp_beta(a, d, g) * matrix_gout_sum(a, g);
60✔
350

351
    // chi_delayed = beta * nu-fission matrix, expanded across delayed groups
352
    for (size_t a = 0; a < n_ang; a++)
30✔
353
      for (size_t d = 0; d < n_dg_; d++)
45✔
354
        for (size_t gin = 0; gin < n_g_; gin++)
90✔
355
          for (size_t gout = 0; gout < n_g_; gout++)
180✔
356
            chi_delayed(a, d, gin, gout) =
120✔
357
              temp_beta(a, d, gin) * temp_matrix(a, gin, gout);
120✔
358
  }
45✔
359

360
  // Normalize chi_prompt so it sums to 1 over outgoing groups
361
  for (size_t a = 0; a < n_ang; a++)
60✔
362
    for (size_t gin = 0; gin < n_g_; gin++) {
90✔
363
      tensor::View<double> row = chi_prompt.slice(a, gin);
60✔
364
      row /= row.sum();
60✔
365
    }
60✔
366

367
  // Normalize chi_delayed so it sums to 1 over outgoing groups
368
  for (size_t a = 0; a < n_ang; a++)
60✔
369
    for (size_t d = 0; d < n_dg_; d++)
90✔
370
      for (size_t gin = 0; gin < n_g_; gin++) {
180✔
371
        tensor::View<double> row = chi_delayed.slice(a, d, gin);
120✔
372
        row /= row.sum();
120✔
373
      }
120✔
374
}
30✔
375

376
void XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
15✔
377
{
378
  // Data is provided separately as prompt + delayed nu-fission and chi
379

380
  // Get the prompt nu-fission matrix
381
  tensor::Tensor<double> temp_matrix_p =
15✔
382
    tensor::zeros<double>({n_ang, n_g_, n_g_});
15✔
383
  read_nd_tensor(xsdata_grp, "prompt-nu-fission", temp_matrix_p, true);
15✔
384

385
  // prompt_nu_fission is the sum over outgoing groups
386
  prompt_nu_fission = temp_matrix_p.sum(2);
15✔
387

388
  // chi_prompt is the nu-fission matrix normalized over outgoing groups
389
  for (size_t a = 0; a < n_ang; a++)
30✔
390
    for (size_t gin = 0; gin < n_g_; gin++)
45✔
391
      for (size_t gout = 0; gout < n_g_; gout++)
90✔
392
        chi_prompt(a, gin, gout) =
60✔
393
          temp_matrix_p(a, gin, gout) / prompt_nu_fission(a, gin);
60✔
394

395
  // Get the delayed nu-fission matrix
396
  tensor::Tensor<double> temp_matrix_d =
15✔
397
    tensor::zeros<double>({n_ang, n_dg_, n_g_, n_g_});
15✔
398
  read_nd_tensor(xsdata_grp, "delayed-nu-fission", temp_matrix_d, true);
15✔
399

400
  // delayed_nu_fission is the sum over outgoing groups
401
  delayed_nu_fission = temp_matrix_d.sum(3);
15✔
402

403
  // chi_delayed is the delayed nu-fission matrix normalized over outgoing
404
  // groups
405
  for (size_t a = 0; a < n_ang; a++)
30✔
406
    for (size_t d = 0; d < n_dg_; d++)
45✔
407
      for (size_t gin = 0; gin < n_g_; gin++)
90✔
408
        for (size_t gout = 0; gout < n_g_; gout++)
180✔
409
          chi_delayed(a, d, gin, gout) =
120✔
410
            temp_matrix_d(a, d, gin, gout) / delayed_nu_fission(a, d, gin);
120✔
411
}
30✔
412

413
void XsData::fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
60✔
414
{
415
  // No beta is provided and there is no prompt/delay distinction.
416
  // Therefore, the code only considers the data as prompt.
417

418
  // Get nu-fission matrix
419
  tensor::Tensor<double> temp_matrix =
60✔
420
    tensor::zeros<double>({n_ang, n_g_, n_g_});
60✔
421
  read_nd_tensor(xsdata_grp, "nu-fission", temp_matrix, true);
60✔
422

423
  // prompt_nu_fission is the sum over outgoing groups
424
  prompt_nu_fission = temp_matrix.sum(2);
60✔
425

426
  // chi_prompt is the nu-fission matrix normalized over outgoing groups
427
  for (size_t a = 0; a < n_ang; a++)
120✔
428
    for (size_t gin = 0; gin < n_g_; gin++)
180✔
429
      for (size_t gout = 0; gout < n_g_; gout++)
360✔
430
        chi_prompt(a, gin, gout) =
240✔
431
          temp_matrix(a, gin, gout) / prompt_nu_fission(a, gin);
240✔
432
}
60✔
433

434
//==============================================================================
435

436
void XsData::fission_from_hdf5(
1,226✔
437
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
438
{
439
  // Get the fission and kappa_fission data xs; these are optional
440
  read_nd_tensor(xsdata_grp, "fission", fission);
1,226✔
441
  read_nd_tensor(xsdata_grp, "kappa-fission", kappa_fission);
1,226✔
442

443
  // Get the data; the strategy for doing so depends on if the data is provided
444
  // as a nu-fission matrix or a set of chi and nu-fission vectors
445
  if (object_exists(xsdata_grp, "chi") ||
1,346✔
446
      object_exists(xsdata_grp, "chi-prompt")) {
120✔
447
    if (n_dg_ == 0) {
1,121✔
448
      fission_vector_no_delayed_from_hdf5(xsdata_grp, n_ang);
1,061✔
449
    } else {
450
      if (object_exists(xsdata_grp, "beta")) {
60✔
451
        fission_vector_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
45✔
452
      } else {
453
        fission_vector_no_beta_from_hdf5(xsdata_grp, n_ang);
15✔
454
      }
455
    }
456
  } else {
457
    if (n_dg_ == 0) {
105✔
458
      fission_matrix_no_delayed_from_hdf5(xsdata_grp, n_ang);
60✔
459
    } else {
460
      if (object_exists(xsdata_grp, "beta")) {
45✔
461
        fission_matrix_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
30✔
462
      } else {
463
        fission_matrix_no_beta_from_hdf5(xsdata_grp, n_ang);
15✔
464
      }
465
    }
466
  }
467

468
  // Combine prompt_nu_fission and delayed_nu_fission into nu_fission
469
  if (n_dg_ == 0) {
1,226✔
470
    nu_fission = prompt_nu_fission;
1,121✔
471
  } else {
472
    nu_fission = prompt_nu_fission + delayed_nu_fission.sum(1);
315✔
473
  }
474
}
1,226✔
475

476
//==============================================================================
477

478
void XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang,
3,366✔
479
  AngleDistributionType scatter_format,
480
  AngleDistributionType final_scatter_format, int order_data)
481
{
482
  if (!object_exists(xsdata_grp, "scatter_data")) {
3,366!
UNCOV
483
    fatal_error("Must provide scatter_data group!");
×
484
  }
485
  hid_t scatt_grp = open_group(xsdata_grp, "scatter_data");
3,366✔
486

487
  // Get the outgoing group boundary indices
488
  tensor::Tensor<int> gmin = tensor::zeros<int>({n_ang, n_g_});
3,366✔
489
  read_nd_tensor(scatt_grp, "g_min", gmin, true);
3,366✔
490
  tensor::Tensor<int> gmax = tensor::zeros<int>({n_ang, n_g_});
3,366✔
491
  read_nd_tensor(scatt_grp, "g_max", gmax, true);
3,366✔
492

493
  // Make gmin and gmax start from 0 vice 1 as they do in the library
494
  gmin -= 1;
3,366✔
495
  gmax -= 1;
3,366✔
496

497
  // Now use this info to find the length of a vector to hold the flattened
498
  // data.
499
  size_t length = order_data * (gmax - gmin + 1).sum();
6,732✔
500

501
  double_4dvec input_scatt(n_ang, double_3dvec(n_g_));
4,955✔
502
  tensor::Tensor<double> temp_arr = tensor::zeros<double>({length});
3,366✔
503
  read_nd_tensor(scatt_grp, "scatter_matrix", temp_arr, true);
3,366✔
504

505
  // Compare the number of orders given with the max order of the problem;
506
  // strip off the superfluous orders if needed
507
  int order_dim;
3,366✔
508
  if (scatter_format == AngleDistributionType::LEGENDRE) {
3,366✔
509
    order_dim = std::min(order_data - 1, settings::max_order) + 1;
3,261✔
510
  } else {
511
    order_dim = order_data;
512
  }
513

514
  // convert the flattened temp_arr to a jagged array for passing to
515
  // scatt data
516
  size_t temp_idx = 0;
3,366✔
517
  for (size_t a = 0; a < n_ang; a++) {
6,822✔
518
    for (size_t gin = 0; gin < n_g_; gin++) {
15,205✔
519
      input_scatt[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
11,749✔
520
      for (size_t i_gout = 0; i_gout < input_scatt[a][gin].size(); i_gout++) {
61,637✔
521
        input_scatt[a][gin][i_gout].resize(order_dim);
49,888✔
522
        for (size_t l = 0; l < order_dim; l++) {
109,811✔
523
          input_scatt[a][gin][i_gout][l] = temp_arr[temp_idx++];
59,923✔
524
        }
525
        // Adjust index for the orders we didnt take
526
        temp_idx += (order_data - order_dim);
49,888✔
527
      }
528
    }
529
  }
530

531
  // Get multiplication matrix
532
  double_3dvec temp_mult(n_ang, double_2dvec(n_g_));
4,955✔
533
  if (object_exists(scatt_grp, "multiplicity_matrix")) {
3,366✔
534
    temp_arr.resize({length / order_data});
721✔
535
    read_nd_tensor(scatt_grp, "multiplicity_matrix", temp_arr);
721✔
536

537
    // convert the flat temp_arr to a jagged array for passing to scatt data
538
    size_t temp_idx = 0;
539
    for (size_t a = 0; a < n_ang; a++) {
1,442✔
540
      for (size_t gin = 0; gin < n_g_; gin++) {
6,303✔
541
        temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
5,582✔
542
        for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
42,081✔
543
          temp_mult[a][gin][i_gout] = temp_arr[temp_idx++];
36,499✔
544
        }
545
      }
546
    }
547
  } else {
548
    // Use a default: multiplicities are 1.0.
549
    for (size_t a = 0; a < n_ang; a++) {
5,380✔
550
      for (size_t gin = 0; gin < n_g_; gin++) {
8,902✔
551
        temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
6,167✔
552
        for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
19,556✔
553
          temp_mult[a][gin][i_gout] = 1.;
13,389✔
554
        }
555
      }
556
    }
557
  }
558
  close_group(scatt_grp);
3,366✔
559

560
  // Finally, convert the Legendre data to tabular, if needed
561
  if (scatter_format == AngleDistributionType::LEGENDRE &&
3,366✔
562
      final_scatter_format == AngleDistributionType::TABULAR) {
3,366✔
563
    for (size_t a = 0; a < n_ang; a++) {
6,117✔
564
      ScattDataLegendre legendre_scatt;
3,081✔
565
      tensor::Tensor<int> in_gmin(gmin.slice(a));
3,081✔
566
      tensor::Tensor<int> in_gmax(gmax.slice(a));
3,081✔
567

568
      legendre_scatt.init(in_gmin, in_gmax, temp_mult[a], input_scatt[a]);
3,081✔
569

570
      // Now create a tabular version of legendre_scatt
571
      convert_legendre_to_tabular(
3,081✔
572
        legendre_scatt, *static_cast<ScattDataTabular*>(scatter[a].get()));
3,081✔
573

574
      scatter_format = final_scatter_format;
3,081✔
575
    }
6,162✔
576
  } else {
577
    // We are sticking with the current representation
578
    // Initialize the ScattData object with this data
579
    for (size_t a = 0; a < n_ang; a++) {
705✔
580
      tensor::Tensor<int> in_gmin(gmin.slice(a));
375✔
581
      tensor::Tensor<int> in_gmax(gmax.slice(a));
375✔
582
      scatter[a]->init(in_gmin, in_gmax, temp_mult[a], input_scatt[a]);
375✔
583
    }
750✔
584
  }
585
}
13,464✔
586

587
//==============================================================================
588

589
void XsData::combine(
2,841✔
590
  const vector<XsData*>& those_xs, const vector<double>& scalars)
591
{
592
  // Combine the non-scattering data
593
  for (size_t i = 0; i < those_xs.size(); i++) {
6,222✔
594
    XsData* that = those_xs[i];
3,381✔
595
    if (!equiv(*that))
3,381!
UNCOV
596
      fatal_error("Cannot combine the XsData objects!");
×
597
    double scalar = scalars[i];
3,381✔
598
    total += scalar * that->total;
3,381✔
599
    absorption += scalar * that->absorption;
3,381✔
600
    if (i == 0) {
3,381✔
601
      inverse_velocity = that->inverse_velocity;
2,841✔
602
    }
603
    if (!that->prompt_nu_fission.empty()) {
3,381✔
604
      nu_fission += scalar * that->nu_fission;
1,241✔
605
      prompt_nu_fission += scalar * that->prompt_nu_fission;
1,241✔
606
      kappa_fission += scalar * that->kappa_fission;
1,241✔
607
      fission += scalar * that->fission;
1,241✔
608
      delayed_nu_fission += scalar * that->delayed_nu_fission;
1,241✔
609
      // Accumulate chi_prompt weighted by total prompt nu-fission
610
      // (summed over energy groups) for this constituent
611
      {
1,241✔
612
        auto pnf_sum = that->prompt_nu_fission.sum(1);
1,241✔
613
        size_t n_ang = chi_prompt.shape(0);
1,241!
614
        size_t n_g = chi_prompt.shape(1);
1,241!
615
        for (size_t a = 0; a < n_ang; a++)
2,572✔
616
          for (size_t gin = 0; gin < n_g; gin++)
6,373✔
617
            for (size_t gout = 0; gout < n_g; gout++)
96,586✔
618
              chi_prompt(a, gin, gout) +=
91,544✔
619
                scalar * pnf_sum(a) * that->chi_prompt(a, gin, gout);
91,544✔
620
      }
1,241✔
621
      // Accumulate chi_delayed weighted by total delayed nu-fission
622
      // (summed over energy groups) for this constituent
623
      {
1,241✔
624
        auto dnf_sum = that->delayed_nu_fission.sum(2);
1,241✔
625
        size_t n_ang = chi_delayed.shape(0);
1,241!
626
        size_t n_dg = chi_delayed.shape(1);
1,241!
627
        size_t n_g = chi_delayed.shape(2);
1,241!
628
        for (size_t a = 0; a < n_ang; a++)
2,572✔
629
          for (size_t d = 0; d < n_dg; d++)
1,601✔
630
            for (size_t gin = 0; gin < n_g; gin++)
810✔
631
              for (size_t gout = 0; gout < n_g; gout++)
1,620✔
632
                chi_delayed(a, d, gin, gout) +=
1,080✔
633
                  scalar * dnf_sum(a, d) * that->chi_delayed(a, d, gin, gout);
1,080✔
634
      }
1,241✔
635
    }
636
    decay_rate += scalar * that->decay_rate;
6,762✔
637
  }
638

639
  // Normalize chi_prompt so it sums to 1 over outgoing groups
640
  {
2,841✔
641
    size_t n_ang = chi_prompt.shape(0);
2,841✔
642
    size_t n_g = chi_prompt.shape(1);
2,841✔
643
    for (size_t a = 0; a < n_ang; a++)
3,992✔
644
      for (size_t gin = 0; gin < n_g; gin++) {
5,833✔
645
        tensor::View<double> row = chi_prompt.slice(a, gin);
4,682✔
646
        row /= row.sum();
4,682✔
647
      }
4,682✔
648
  }
649
  // Normalize chi_delayed so it sums to 1 over outgoing groups
650
  {
2,841✔
651
    size_t n_ang = chi_delayed.shape(0);
2,841✔
652
    size_t n_dg = chi_delayed.shape(1);
2,841✔
653
    size_t n_g = chi_delayed.shape(2);
2,841✔
654
    for (size_t a = 0; a < n_ang; a++)
3,992✔
655
      for (size_t d = 0; d < n_dg; d++)
1,421✔
656
        for (size_t gin = 0; gin < n_g; gin++) {
810✔
657
          tensor::View<double> row = chi_delayed.slice(a, d, gin);
540✔
658
          row /= row.sum();
540✔
659
        }
540✔
660
  }
661

662
  // Allow the ScattData object to combine itself
663
  for (size_t a = 0; a < total.shape(0); a++) {
11,544!
664
    // Build vector of the scattering objects to incorporate
665
    vector<ScattData*> those_scatts(those_xs.size());
2,931✔
666
    for (size_t i = 0; i < those_xs.size(); i++) {
6,402✔
667
      those_scatts[i] = those_xs[i]->scatter[a].get();
3,471✔
668
    }
669

670
    // Now combine these guys
671
    scatter[a]->combine(those_scatts, scalars);
2,931✔
672
  }
2,931✔
673
}
2,841✔
674

675
//==============================================================================
676

677
bool XsData::equiv(const XsData& that)
3,381✔
678
{
679
  return (absorption.shape() == that.absorption.shape());
3,381✔
680
}
681

682
} // 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