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

openmc-dev / openmc / 21991279157

13 Feb 2026 02:53PM UTC coverage: 81.82% (-0.06%) from 81.875%
21991279157

Pull #3805

github

web-flow
Merge 0a7a80411 into bcb939520
Pull Request #3805: Remove xtensor and xtl Dependencies

17242 of 24268 branches covered (71.05%)

Branch coverage included in aggregate %.

977 of 1013 new or added lines in 39 files covered. (96.45%)

404 existing lines in 8 files now uncovered.

57420 of 66983 relevant lines covered (85.72%)

45458907.73 hits per line

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

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

29
  // check to make sure scatter format is OK before we allocate
30
  if (scatter_format != AngleDistributionType::HISTOGRAM &&
5,760✔
31
      scatter_format != AngleDistributionType::TABULAR &&
392!
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_};
5,760✔
37
  total = tensor::zeros<double>(shape);
5,760✔
38
  absorption = tensor::zeros<double>(shape);
5,760✔
39
  inverse_velocity = tensor::zeros<double>(shape);
5,760✔
40
  if (fissionable) {
5,760✔
41
    fission = tensor::zeros<double>(shape);
2,134✔
42
    nu_fission = tensor::zeros<double>(shape);
2,134✔
43
    prompt_nu_fission = tensor::zeros<double>(shape);
2,134✔
44
    kappa_fission = tensor::zeros<double>(shape);
2,134✔
45
  }
46

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

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

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

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

65
  for (int a = 0; a < n_ang; a++) {
11,688✔
66
    if (scatter_format == AngleDistributionType::HISTOGRAM) {
5,928✔
67
      scatter.emplace_back(new ScattDataHistogram);
112✔
68
    } else if (scatter_format == AngleDistributionType::TABULAR) {
5,816✔
69
      scatter.emplace_back(new ScattDataTabular);
5,340✔
70
    } else if (scatter_format == AngleDistributionType::LEGENDRE) {
476!
71
      scatter.emplace_back(new ScattDataLegendre);
476✔
72
    }
73
  }
74
}
5,760✔
75

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

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

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

96
  // Get scattering data
97
  scatter_from_hdf5(
3,125✔
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++)
14,071✔
103
    if (absorption.data()[i] == 0.0)
10,946!
NEW
104
      absorption.data()[i] = 1.e-10;
×
105

106
  // Get or calculate the total x/s
107
  if (object_exists(xsdata_grp, "total")) {
3,125!
108
    read_nd_tensor(xsdata_grp, "total", total);
3,125✔
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++)
14,071✔
120
    if (total.data()[i] == 0.0)
10,946!
NEW
121
      total.data()[i] = 1.e-10;
×
122
}
3,125✔
123

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

126
void XsData::fission_vector_beta_from_hdf5(
42✔
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({n_ang, n_g_}, 0.);
42✔
133
  read_nd_tensor(xsdata_grp, "chi", temp_chi, true);
42✔
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++) {
84✔
137
    tensor::View<double> row = temp_chi.slice(a);
42✔
138
    row /= row.sum();
42✔
139
  }
42✔
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++)
84✔
144
    for (size_t gin = 0; gin < n_g_; gin++)
126✔
145
      chi_prompt.slice(a, gin) = temp_chi.slice(a);
84✔
146

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

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

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

168
    // prompt_nu_fission = (1 - sum_of_beta) * nu_fission
169
    auto beta_sum = temp_beta.sum(1);
28✔
170
    for (size_t a = 0; a < n_ang; a++)
56✔
171
      for (size_t g = 0; g < n_g_; g++)
84✔
172
        prompt_nu_fission(a, g) = temp_nufiss(a, g) * (1.0 - beta_sum(a));
56✔
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++)
56✔
177
      for (size_t d = 0; d < n_dg_; d++)
140✔
178
        for (size_t g = 0; g < n_g_; g++)
336✔
179
          delayed_nu_fission(a, d, g) = temp_beta(a, d) * temp_nufiss(a, g);
224✔
180
  } else if (beta_ndims == ndim_target + 1) {
42!
181
    tensor::Tensor<double> temp_beta({n_ang, n_dg_, n_g_}, 0.);
14✔
182
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
14✔
183

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

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

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

204
  // Get chi-prompt
205
  tensor::Tensor<double> temp_chi_p({n_ang, n_g_}, 0.);
14✔
206
  read_nd_tensor(xsdata_grp, "chi-prompt", temp_chi_p, true);
14✔
207

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

214
  // Get chi-delayed
215
  tensor::Tensor<double> temp_chi_d({n_ang, n_dg_, n_g_}, 0.);
14✔
216
  read_nd_tensor(xsdata_grp, "chi-delayed", temp_chi_d, true);
14✔
217

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

226
  // Replicate the prompt spectrum across all incoming groups
227
  for (size_t a = 0; a < n_ang; a++)
28✔
228
    for (size_t gin = 0; gin < n_g_; gin++)
42✔
229
      chi_prompt.slice(a, gin) = temp_chi_p.slice(a);
28✔
230

231
  // Replicate the delayed spectrum across all incoming groups
232
  for (size_t a = 0; a < n_ang; a++)
28✔
233
    for (size_t d = 0; d < n_dg_; d++)
42✔
234
      for (size_t gin = 0; gin < n_g_; gin++)
84✔
235
        chi_delayed.slice(a, d, gin) = temp_chi_d.slice(a, d);
56✔
236

237
  // Get prompt and delayed nu-fission directly
238
  read_nd_tensor(xsdata_grp, "prompt-nu-fission", prompt_nu_fission, true);
14✔
239
  read_nd_tensor(xsdata_grp, "delayed-nu-fission", delayed_nu_fission, true);
14✔
240
}
14✔
241

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

247
  // Get chi
248
  tensor::Tensor<double> temp_chi({n_ang, n_g_}, 0.);
990✔
249
  read_nd_tensor(xsdata_grp, "chi", temp_chi, true);
990✔
250

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

257
  // Replicate the energy spectrum across all incoming groups
258
  for (size_t a = 0; a < n_ang; a++)
2,064✔
259
    for (size_t gin = 0; gin < n_g_; gin++)
5,442✔
260
      chi_prompt.slice(a, gin) = temp_chi.slice(a);
4,368✔
261

262
  // Get nu-fission directly
263
  read_nd_tensor(xsdata_grp, "nu-fission", prompt_nu_fission, true);
990✔
264
}
990✔
265

266
//==============================================================================
267

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

273
  // Get nu-fission matrix
274
  tensor::Tensor<double> temp_matrix({n_ang, n_g_, n_g_}, 0.);
28✔
275
  read_nd_tensor(xsdata_grp, "nu-fission", temp_matrix, true);
28✔
276

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

288
    auto beta_sum = temp_beta.sum(1);
14✔
289
    auto matrix_gout_sum = temp_matrix.sum(2);
14✔
290

291
    // prompt_nu_fission = sum_gout(matrix) * (1 - beta_total)
292
    for (size_t a = 0; a < n_ang; a++)
28✔
293
      for (size_t g = 0; g < n_g_; g++)
42✔
294
        prompt_nu_fission(a, g) = matrix_gout_sum(a, g) * (1.0 - beta_sum(a));
28✔
295

296
    // chi_prompt = (1 - beta_total) * nu-fission matrix (unnormalized)
297
    for (size_t a = 0; a < n_ang; a++)
28✔
298
      for (size_t gin = 0; gin < n_g_; gin++)
42✔
299
        for (size_t gout = 0; gout < n_g_; gout++)
84✔
300
          chi_prompt(a, gin, gout) =
56✔
301
            (1.0 - beta_sum(a)) * temp_matrix(a, gin, gout);
56✔
302

303
    // Delayed nu-fission is the outer product of the delayed neutron
304
    // fraction (beta) and the total fission rate summed over outgoing groups
305
    for (size_t a = 0; a < n_ang; a++)
28✔
306
      for (size_t d = 0; d < n_dg_; d++)
42✔
307
        for (size_t g = 0; g < n_g_; g++)
84✔
308
          delayed_nu_fission(a, d, g) = temp_beta(a, d) * matrix_gout_sum(a, g);
56✔
309

310
    // chi_delayed = beta * nu-fission matrix, expanded across delayed groups
311
    for (size_t a = 0; a < n_ang; a++)
28✔
312
      for (size_t d = 0; d < n_dg_; d++)
42✔
313
        for (size_t gin = 0; gin < n_g_; gin++)
84✔
314
          for (size_t gout = 0; gout < n_g_; gout++)
168✔
315
            chi_delayed(a, d, gin, gout) =
112✔
316
              temp_beta(a, d) * temp_matrix(a, gin, gout);
112✔
317

318
  } else if (beta_ndims == ndim_target + 1) {
28!
319
    tensor::Tensor<double> temp_beta({n_ang, n_dg_, n_g_}, 0.);
14✔
320
    read_nd_tensor(xsdata_grp, "beta", temp_beta, true);
14✔
321

322
    auto beta_sum = temp_beta.sum(1);
14✔
323
    auto matrix_gout_sum = temp_matrix.sum(2);
14✔
324

325
    // prompt_nu_fission = sum_gout(matrix) * (1 - beta_total)
326
    // Here beta is energy-dependent, so beta_sum is 2D [n_ang, n_g]
327
    for (size_t a = 0; a < n_ang; a++)
28✔
328
      for (size_t g = 0; g < n_g_; g++)
42✔
329
        prompt_nu_fission(a, g) =
28✔
330
          matrix_gout_sum(a, g) * (1.0 - beta_sum(a, g));
28✔
331

332
    // chi_prompt = (1 - beta_sum) * nu-fission matrix (unnormalized)
333
    for (size_t a = 0; a < n_ang; a++)
28✔
334
      for (size_t gin = 0; gin < n_g_; gin++)
42✔
335
        for (size_t gout = 0; gout < n_g_; gout++)
84✔
336
          chi_prompt(a, gin, gout) =
56✔
337
            (1.0 - beta_sum(a, gin)) * temp_matrix(a, gin, gout);
56✔
338

339
    // Delayed nu-fission: beta is energy-dependent [n_ang, n_dg, n_g],
340
    // scale by total fission rate summed over outgoing groups
341
    for (size_t a = 0; a < n_ang; a++)
28✔
342
      for (size_t d = 0; d < n_dg_; d++)
42✔
343
        for (size_t g = 0; g < n_g_; g++)
84✔
344
          delayed_nu_fission(a, d, g) =
56✔
345
            temp_beta(a, d, g) * matrix_gout_sum(a, g);
56✔
346

347
    // chi_delayed = beta * nu-fission matrix, expanded across delayed groups
348
    for (size_t a = 0; a < n_ang; a++)
28✔
349
      for (size_t d = 0; d < n_dg_; d++)
42✔
350
        for (size_t gin = 0; gin < n_g_; gin++)
84✔
351
          for (size_t gout = 0; gout < n_g_; gout++)
168✔
352
            chi_delayed(a, d, gin, gout) =
112✔
353
              temp_beta(a, d, gin) * temp_matrix(a, gin, gout);
112✔
354
  }
14✔
355

356
  // Normalize chi_prompt so it sums to 1 over outgoing groups
357
  for (size_t a = 0; a < n_ang; a++)
56✔
358
    for (size_t gin = 0; gin < n_g_; gin++) {
84✔
359
      tensor::View<double> row = chi_prompt.slice(a, gin);
56✔
360
      row /= row.sum();
56✔
361
    }
56✔
362

363
  // Normalize chi_delayed so it sums to 1 over outgoing groups
364
  for (size_t a = 0; a < n_ang; a++)
56✔
365
    for (size_t d = 0; d < n_dg_; d++)
84✔
366
      for (size_t gin = 0; gin < n_g_; gin++) {
168✔
367
        tensor::View<double> row = chi_delayed.slice(a, d, gin);
112✔
368
        row /= row.sum();
112✔
369
      }
112✔
370
}
28✔
371

372
void XsData::fission_matrix_no_beta_from_hdf5(hid_t xsdata_grp, size_t n_ang)
14✔
373
{
374
  // Data is provided separately as prompt + delayed nu-fission and chi
375

376
  // Get the prompt nu-fission matrix
377
  tensor::Tensor<double> temp_matrix_p({n_ang, n_g_, n_g_}, 0.);
14✔
378
  read_nd_tensor(xsdata_grp, "prompt-nu-fission", temp_matrix_p, true);
14✔
379

380
  // prompt_nu_fission is the sum over outgoing groups
381
  prompt_nu_fission = temp_matrix_p.sum(2);
14✔
382

383
  // chi_prompt is the nu-fission matrix normalized over outgoing groups
384
  for (size_t a = 0; a < n_ang; a++)
28✔
385
    for (size_t gin = 0; gin < n_g_; gin++)
42✔
386
      for (size_t gout = 0; gout < n_g_; gout++)
84✔
387
        chi_prompt(a, gin, gout) =
56✔
388
          temp_matrix_p(a, gin, gout) / prompt_nu_fission(a, gin);
56✔
389

390
  // Get the delayed nu-fission matrix
391
  tensor::Tensor<double> temp_matrix_d({n_ang, n_dg_, n_g_, n_g_}, 0.);
14✔
392
  read_nd_tensor(xsdata_grp, "delayed-nu-fission", temp_matrix_d, true);
14✔
393

394
  // delayed_nu_fission is the sum over outgoing groups
395
  delayed_nu_fission = temp_matrix_d.sum(3);
14✔
396

397
  // chi_delayed is the delayed nu-fission matrix normalized over outgoing
398
  // groups
399
  for (size_t a = 0; a < n_ang; a++)
28✔
400
    for (size_t d = 0; d < n_dg_; d++)
42✔
401
      for (size_t gin = 0; gin < n_g_; gin++)
84✔
402
        for (size_t gout = 0; gout < n_g_; gout++)
168✔
403
          chi_delayed(a, d, gin, gout) =
112✔
404
            temp_matrix_d(a, d, gin, gout) / delayed_nu_fission(a, d, gin);
112✔
405
}
14✔
406

407
void XsData::fission_matrix_no_delayed_from_hdf5(hid_t xsdata_grp, size_t n_ang)
56✔
408
{
409
  // No beta is provided and there is no prompt/delay distinction.
410
  // Therefore, the code only considers the data as prompt.
411

412
  // Get nu-fission matrix
413
  tensor::Tensor<double> temp_matrix({n_ang, n_g_, n_g_}, 0.);
56✔
414
  read_nd_tensor(xsdata_grp, "nu-fission", temp_matrix, true);
56✔
415

416
  // prompt_nu_fission is the sum over outgoing groups
417
  prompt_nu_fission = temp_matrix.sum(2);
56✔
418

419
  // chi_prompt is the nu-fission matrix normalized over outgoing groups
420
  for (size_t a = 0; a < n_ang; a++)
112✔
421
    for (size_t gin = 0; gin < n_g_; gin++)
168✔
422
      for (size_t gout = 0; gout < n_g_; gout++)
336✔
423
        chi_prompt(a, gin, gout) =
224✔
424
          temp_matrix(a, gin, gout) / prompt_nu_fission(a, gin);
224✔
425
}
56✔
426

427
//==============================================================================
428

429
void XsData::fission_from_hdf5(
1,144✔
430
  hid_t xsdata_grp, size_t n_ang, bool is_isotropic)
431
{
432
  // Get the fission and kappa_fission data xs; these are optional
433
  read_nd_tensor(xsdata_grp, "fission", fission);
1,144✔
434
  read_nd_tensor(xsdata_grp, "kappa-fission", kappa_fission);
1,144✔
435

436
  // Get the data; the strategy for doing so depends on if the data is provided
437
  // as a nu-fission matrix or a set of chi and nu-fission vectors
438
  if (object_exists(xsdata_grp, "chi") ||
1,256✔
439
      object_exists(xsdata_grp, "chi-prompt")) {
112✔
440
    if (n_dg_ == 0) {
1,046✔
441
      fission_vector_no_delayed_from_hdf5(xsdata_grp, n_ang);
990✔
442
    } else {
443
      if (object_exists(xsdata_grp, "beta")) {
56✔
444
        fission_vector_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
42✔
445
      } else {
446
        fission_vector_no_beta_from_hdf5(xsdata_grp, n_ang);
14✔
447
      }
448
    }
449
  } else {
450
    if (n_dg_ == 0) {
98✔
451
      fission_matrix_no_delayed_from_hdf5(xsdata_grp, n_ang);
56✔
452
    } else {
453
      if (object_exists(xsdata_grp, "beta")) {
42✔
454
        fission_matrix_beta_from_hdf5(xsdata_grp, n_ang, is_isotropic);
28✔
455
      } else {
456
        fission_matrix_no_beta_from_hdf5(xsdata_grp, n_ang);
14✔
457
      }
458
    }
459
  }
460

461
  // Combine prompt_nu_fission and delayed_nu_fission into nu_fission
462
  if (n_dg_ == 0) {
1,144✔
463
    nu_fission = prompt_nu_fission;
1,046✔
464
  } else {
465
    nu_fission = prompt_nu_fission + delayed_nu_fission.sum(1);
98✔
466
  }
467
}
1,144✔
468

469
//==============================================================================
470

471
void XsData::scatter_from_hdf5(hid_t xsdata_grp, size_t n_ang,
3,125✔
472
  AngleDistributionType scatter_format,
473
  AngleDistributionType final_scatter_format, int order_data)
474
{
475
  if (!object_exists(xsdata_grp, "scatter_data")) {
3,125!
476
    fatal_error("Must provide scatter_data group!");
×
477
  }
478
  hid_t scatt_grp = open_group(xsdata_grp, "scatter_data");
3,125✔
479

480
  // Get the outgoing group boundary indices
481
  tensor::Tensor<int> gmin({n_ang, n_g_}, 0.);
3,125✔
482
  read_nd_tensor(scatt_grp, "g_min", gmin, true);
3,125✔
483
  tensor::Tensor<int> gmax({n_ang, n_g_}, 0.);
3,125✔
484
  read_nd_tensor(scatt_grp, "g_max", gmax, true);
3,125✔
485

486
  // Make gmin and gmax start from 0 vice 1 as they do in the library
487
  gmin -= 1;
3,125✔
488
  gmax -= 1;
3,125✔
489

490
  // Now use this info to find the length of a vector to hold the flattened
491
  // data.
492
  size_t length = order_data * (gmax - gmin + 1).sum();
3,125✔
493

494
  double_4dvec input_scatt(n_ang, double_3dvec(n_g_));
6,250✔
495
  tensor::Tensor<double> temp_arr({length}, 0.);
3,125✔
496
  read_nd_tensor(scatt_grp, "scatter_matrix", temp_arr, true);
3,125✔
497

498
  // Compare the number of orders given with the max order of the problem;
499
  // strip off the superfluous orders if needed
500
  int order_dim;
501
  if (scatter_format == AngleDistributionType::LEGENDRE) {
3,125✔
502
    order_dim = std::min(order_data - 1, settings::max_order) + 1;
3,013✔
503
  } else {
504
    order_dim = order_data;
112✔
505
  }
506

507
  // convert the flattened temp_arr to a jagged array for passing to
508
  // scatt data
509
  size_t temp_idx = 0;
3,125✔
510
  for (size_t a = 0; a < n_ang; a++) {
6,334✔
511
    for (size_t gin = 0; gin < n_g_; gin++) {
14,155✔
512
      input_scatt[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
10,946✔
513
      for (size_t i_gout = 0; i_gout < input_scatt[a][gin].size(); i_gout++) {
57,480✔
514
        input_scatt[a][gin][i_gout].resize(order_dim);
46,534✔
515
        for (size_t l = 0; l < order_dim; l++) {
102,434✔
516
          input_scatt[a][gin][i_gout][l] = temp_arr[temp_idx++];
55,900✔
517
        }
518
        // Adjust index for the orders we didnt take
519
        temp_idx += (order_data - order_dim);
46,534✔
520
      }
521
    }
522
  }
523

524
  // Get multiplication matrix
525
  double_3dvec temp_mult(n_ang, double_2dvec(n_g_));
6,250✔
526
  if (object_exists(scatt_grp, "multiplicity_matrix")) {
3,125✔
527
    temp_arr.resize({length / order_data});
673✔
528
    read_nd_tensor(scatt_grp, "multiplicity_matrix", temp_arr);
673✔
529

530
    // convert the flat temp_arr to a jagged array for passing to scatt data
531
    size_t temp_idx = 0;
673✔
532
    for (size_t a = 0; a < n_ang; a++) {
1,346✔
533
      for (size_t gin = 0; gin < n_g_; gin++) {
5,883✔
534
        temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
5,210✔
535
        for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
39,276✔
536
          temp_mult[a][gin][i_gout] = temp_arr[temp_idx++];
34,066✔
537
        }
538
      }
539
    }
540
  } else {
541
    // Use a default: multiplicities are 1.0.
542
    for (size_t a = 0; a < n_ang; a++) {
4,988✔
543
      for (size_t gin = 0; gin < n_g_; gin++) {
8,272✔
544
        temp_mult[a][gin].resize(gmax(a, gin) - gmin(a, gin) + 1);
5,736✔
545
        for (size_t i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) {
18,204✔
546
          temp_mult[a][gin][i_gout] = 1.;
12,468✔
547
        }
548
      }
549
    }
550
  }
551
  close_group(scatt_grp);
3,125✔
552

553
  // Finally, convert the Legendre data to tabular, if needed
554
  if (scatter_format == AngleDistributionType::LEGENDRE &&
3,125✔
555
      final_scatter_format == AngleDistributionType::TABULAR) {
556
    for (size_t a = 0; a < n_ang; a++) {
5,676✔
557
      ScattDataLegendre legendre_scatt;
2,859✔
558
      tensor::Tensor<int> in_gmin(gmin.slice(a));
2,859✔
559
      tensor::Tensor<int> in_gmax(gmax.slice(a));
2,859✔
560

561
      legendre_scatt.init(in_gmin, in_gmax, temp_mult[a], input_scatt[a]);
2,859✔
562

563
      // Now create a tabular version of legendre_scatt
564
      convert_legendre_to_tabular(
2,859✔
565
        legendre_scatt, *static_cast<ScattDataTabular*>(scatter[a].get()));
2,859✔
566

567
      scatter_format = final_scatter_format;
2,859✔
568
    }
2,859✔
569
  } else {
2,817✔
570
    // We are sticking with the current representation
571
    // Initialize the ScattData object with this data
572
    for (size_t a = 0; a < n_ang; a++) {
658✔
573
      tensor::Tensor<int> in_gmin(gmin.slice(a));
350✔
574
      tensor::Tensor<int> in_gmax(gmax.slice(a));
350✔
575
      scatter[a]->init(in_gmin, in_gmax, temp_mult[a], input_scatt[a]);
350✔
576
    }
350✔
577
  }
578
}
3,125✔
579

580
//==============================================================================
581

582
void XsData::combine(
2,635✔
583
  const vector<XsData*>& those_xs, const vector<double>& scalars)
584
{
585
  // Combine the non-scattering data
586
  for (size_t i = 0; i < those_xs.size(); i++) {
5,774✔
587
    XsData* that = those_xs[i];
3,139✔
588
    if (!equiv(*that))
3,139!
589
      fatal_error("Cannot combine the XsData objects!");
×
590
    double scalar = scalars[i];
3,139✔
591
    total += scalar * that->total;
3,139✔
592
    absorption += scalar * that->absorption;
3,139✔
593
    if (i == 0) {
3,139✔
594
      inverse_velocity = that->inverse_velocity;
2,635✔
595
    }
596
    if (!that->prompt_nu_fission.empty()) {
3,139✔
597
      nu_fission += scalar * that->nu_fission;
1,158✔
598
      prompt_nu_fission += scalar * that->prompt_nu_fission;
1,158✔
599
      kappa_fission += scalar * that->kappa_fission;
1,158✔
600
      fission += scalar * that->fission;
1,158✔
601
      delayed_nu_fission += scalar * that->delayed_nu_fission;
1,158✔
602
      // Accumulate chi_prompt weighted by total prompt nu-fission
603
      // (summed over energy groups) for this constituent
604
      {
605
        auto pnf_sum = that->prompt_nu_fission.sum(1);
1,158✔
606
        size_t n_ang = chi_prompt.shape(0);
1,158✔
607
        size_t n_g = chi_prompt.shape(1);
1,158✔
608
        for (size_t a = 0; a < n_ang; a++)
2,400✔
609
          for (size_t gin = 0; gin < n_g; gin++)
5,946✔
610
            for (size_t gout = 0; gout < n_g; gout++)
90,132✔
611
              chi_prompt(a, gin, gout) +=
85,428✔
612
                scalar * pnf_sum(a) * that->chi_prompt(a, gin, gout);
85,428✔
613
      }
1,158✔
614
      // Accumulate chi_delayed weighted by total delayed nu-fission
615
      // (summed over energy groups) for this constituent
616
      {
617
        auto dnf_sum = that->delayed_nu_fission.sum(2);
1,158✔
618
        size_t n_ang = chi_delayed.shape(0);
1,158✔
619
        size_t n_dg = chi_delayed.shape(1);
1,158✔
620
        size_t n_g = chi_delayed.shape(2);
1,158✔
621
        for (size_t a = 0; a < n_ang; a++)
2,400✔
622
          for (size_t d = 0; d < n_dg; d++)
1,494✔
623
            for (size_t gin = 0; gin < n_g; gin++)
756✔
624
              for (size_t gout = 0; gout < n_g; gout++)
1,512✔
625
                chi_delayed(a, d, gin, gout) +=
1,008✔
626
                  scalar * dnf_sum(a, d) * that->chi_delayed(a, d, gin, gout);
1,008✔
627
      }
1,158✔
628
    }
629
    decay_rate += scalar * that->decay_rate;
3,139✔
630
  }
631

632
  // Normalize chi_prompt so it sums to 1 over outgoing groups
633
  {
634
    size_t n_ang = chi_prompt.shape(0);
2,635✔
635
    size_t n_g = chi_prompt.shape(1);
2,635✔
636
    for (size_t a = 0; a < n_ang; a++)
3,709✔
637
      for (size_t gin = 0; gin < n_g; gin++) {
5,442✔
638
        tensor::View<double> row = chi_prompt.slice(a, gin);
4,368✔
639
        row /= row.sum();
4,368✔
640
      }
4,368✔
641
  }
642
  // Normalize chi_delayed so it sums to 1 over outgoing groups
643
  {
644
    size_t n_ang = chi_delayed.shape(0);
2,635✔
645
    size_t n_dg = chi_delayed.shape(1);
2,635✔
646
    size_t n_g = chi_delayed.shape(2);
2,635✔
647
    for (size_t a = 0; a < n_ang; a++)
3,709✔
648
      for (size_t d = 0; d < n_dg; d++)
1,326✔
649
        for (size_t gin = 0; gin < n_g; gin++) {
756✔
650
          tensor::View<double> row = chi_delayed.slice(a, d, gin);
504✔
651
          row /= row.sum();
504✔
652
        }
504✔
653
  }
654

655
  // Allow the ScattData object to combine itself
656
  for (size_t a = 0; a < total.shape(0); a++) {
5,354✔
657
    // Build vector of the scattering objects to incorporate
658
    vector<ScattData*> those_scatts(those_xs.size());
2,719✔
659
    for (size_t i = 0; i < those_xs.size(); i++) {
5,942✔
660
      those_scatts[i] = those_xs[i]->scatter[a].get();
3,223✔
661
    }
662

663
    // Now combine these guys
664
    scatter[a]->combine(those_scatts, scalars);
2,719✔
665
  }
2,719✔
666
}
2,635✔
667

668
//==============================================================================
669

670
bool XsData::equiv(const XsData& that)
3,139✔
671
{
672
  return (absorption.shape() == that.absorption.shape());
3,139✔
673
}
674

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