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

openmc-dev / openmc / 13183515203

06 Feb 2025 04:36PM UTC coverage: 82.601% (-2.3%) from 84.867%
13183515203

Pull #3087

github

web-flow
Merge d68c72d5e into 6e0f156d3
Pull Request #3087: wheel building with scikit build core

107123 of 129687 relevant lines covered (82.6%)

12608333.34 hits per line

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

95.02
/src/eigenvalue.cpp
1
#include "openmc/eigenvalue.h"
2

3
#include "xtensor/xbuilder.hpp"
4
#include "xtensor/xmath.hpp"
5
#include "xtensor/xtensor.hpp"
6
#include "xtensor/xview.hpp"
7

8
#include "openmc/array.h"
9
#include "openmc/bank.h"
10
#include "openmc/capi.h"
11
#include "openmc/constants.h"
12
#include "openmc/error.h"
13
#include "openmc/hdf5_interface.h"
14
#include "openmc/math_functions.h"
15
#include "openmc/mesh.h"
16
#include "openmc/message_passing.h"
17
#include "openmc/random_lcg.h"
18
#include "openmc/search.h"
19
#include "openmc/settings.h"
20
#include "openmc/simulation.h"
21
#include "openmc/tallies/tally.h"
22
#include "openmc/timer.h"
23

24
#include <algorithm> // for min
25
#include <cmath>     // for sqrt, abs, pow
26
#include <iterator>  // for back_inserter
27
#include <limits>    //for infinity
28
#include <string>
29

30
namespace openmc {
31

32
//==============================================================================
33
// Global variables
34
//==============================================================================
35

36
namespace simulation {
37

38
double keff_generation;
39
array<double, 2> k_sum;
40
vector<double> entropy;
41
xt::xtensor<double, 1> source_frac;
42

43
} // namespace simulation
44

45
//==============================================================================
46
// Non-member functions
47
//==============================================================================
48

49
void calculate_generation_keff()
64,480✔
50
{
51
  const auto& gt = simulation::global_tallies;
64,480✔
52

53
  // Get keff for this generation by subtracting off the starting value
54
  simulation::keff_generation =
64,480✔
55
    gt(GlobalTally::K_TRACKLENGTH, TallyResult::VALUE) -
64,480✔
56
    simulation::keff_generation;
57

58
  double keff_reduced;
59
#ifdef OPENMC_MPI
60
  if (settings::solver_type != SolverType::RANDOM_RAY) {
36,295✔
61
    // Combine values across all processors
62
    MPI_Allreduce(&simulation::keff_generation, &keff_reduced, 1, MPI_DOUBLE,
35,095✔
63
      MPI_SUM, mpi::intracomm);
64
  } else {
65
    // If using random ray, MPI parallelism is provided by domain replication.
66
    // As such, all fluxes will be reduced at the end of each transport sweep,
67
    // such that all ranks have identical scalar flux vectors, and will all
68
    // independently compute the same value of k. Thus, there is no need to
69
    // perform any additional MPI reduction here.
70
    keff_reduced = simulation::keff_generation;
1,200✔
71
  }
72
#else
73
  keff_reduced = simulation::keff_generation;
28,185✔
74
#endif
75

76
  // Normalize single batch estimate of k
77
  // TODO: This should be normalized by total_weight, not by n_particles
78
  if (settings::solver_type != SolverType::RANDOM_RAY) {
64,480✔
79
    keff_reduced /= settings::n_particles;
62,440✔
80
  }
81

82
  simulation::k_generation.push_back(keff_reduced);
64,480✔
83
}
64,480✔
84

85
void synchronize_bank()
62,440✔
86
{
87
  simulation::time_bank.start();
62,440✔
88

89
  // In order to properly understand the fission bank algorithm, you need to
90
  // think of the fission and source bank as being one global array divided
91
  // over multiple processors. At the start, each processor has a random amount
92
  // of fission bank sites -- each processor needs to know the total number of
93
  // sites in order to figure out the probability for selecting
94
  // sites. Furthermore, each proc also needs to know where in the 'global'
95
  // fission bank its own sites starts in order to ensure reproducibility by
96
  // skipping ahead to the proper seed.
97

98
#ifdef OPENMC_MPI
99
  int64_t start = 0;
35,095✔
100
  int64_t n_bank = simulation::fission_bank.size();
35,095✔
101
  MPI_Exscan(&n_bank, &start, 1, MPI_INT64_T, MPI_SUM, mpi::intracomm);
35,095✔
102

103
  // While we would expect the value of start on rank 0 to be 0, the MPI
104
  // standard says that the receive buffer on rank 0 is undefined and not
105
  // significant
106
  if (mpi::rank == 0)
35,095✔
107
    start = 0;
19,685✔
108

109
  int64_t finish = start + simulation::fission_bank.size();
35,095✔
110
  int64_t total = finish;
35,095✔
111
  MPI_Bcast(&total, 1, MPI_INT64_T, mpi::n_procs - 1, mpi::intracomm);
35,095✔
112

113
#else
114
  int64_t start = 0;
27,345✔
115
  int64_t finish = simulation::fission_bank.size();
27,345✔
116
  int64_t total = finish;
27,345✔
117
#endif
118

119
  // If there are not that many particles per generation, it's possible that no
120
  // fission sites were created at all on a single processor. Rather than add
121
  // extra logic to treat this circumstance, we really want to ensure the user
122
  // runs enough particles to avoid this in the first place.
123

124
  if (simulation::fission_bank.size() == 0) {
62,440✔
125
    fatal_error(
×
126
      "No fission sites banked on MPI rank " + std::to_string(mpi::rank));
×
127
  }
128

129
  // Make sure all processors start at the same point for random sampling. Then
130
  // skip ahead in the sequence using the starting index in the 'global'
131
  // fission bank for each processor.
132

133
  int64_t id = simulation::total_gen + overall_generation();
62,440✔
134
  uint64_t seed = init_seed(id, STREAM_TRACKING);
62,440✔
135
  advance_prn_seed(start, &seed);
62,440✔
136

137
  // Determine how many fission sites we need to sample from the source bank
138
  // and the probability for selecting a site.
139

140
  int64_t sites_needed;
141
  if (total < settings::n_particles) {
62,440✔
142
    sites_needed = settings::n_particles % total;
32,650✔
143
  } else {
144
    sites_needed = settings::n_particles;
29,790✔
145
  }
146
  double p_sample = static_cast<double>(sites_needed) / total;
62,440✔
147

148
  simulation::time_bank_sample.start();
62,440✔
149

150
  // ==========================================================================
151
  // SAMPLE N_PARTICLES FROM FISSION BANK AND PLACE IN TEMP_SITES
152

153
  // Allocate temporary source bank -- we don't really know how many fission
154
  // sites were created, so overallocate by a factor of 3
155
  int64_t index_temp = 0;
62,440✔
156
  vector<SourceSite> temp_sites(3 * simulation::work_per_rank);
62,440✔
157

158
  for (int64_t i = 0; i < simulation::fission_bank.size(); i++) {
137,274,605✔
159
    const auto& site = simulation::fission_bank[i];
137,212,165✔
160

161
    // If there are less than n_particles particles banked, automatically add
162
    // int(n_particles/total) sites to temp_sites. For example, if you need
163
    // 1000 and 300 were banked, this would add 3 source sites per banked site
164
    // and the remaining 100 would be randomly sampled.
165
    if (total < settings::n_particles) {
137,212,165✔
166
      for (int64_t j = 1; j <= settings::n_particles / total; ++j) {
137,583,359✔
167
        temp_sites[index_temp] = site;
69,013,352✔
168
        ++index_temp;
69,013,352✔
169
      }
170
    }
171

172
    // Randomly sample sites needed
173
    if (prn(&seed) < p_sample) {
137,212,165✔
174
      temp_sites[index_temp] = site;
68,278,196✔
175
      ++index_temp;
68,278,196✔
176
    }
177
  }
178

179
  // At this point, the sampling of source sites is done and now we need to
180
  // figure out where to send source sites. Since it is possible that one
181
  // processor's share of the source bank spans more than just the immediate
182
  // neighboring processors, we have to perform an ALLGATHER to determine the
183
  // indices for all processors
184

185
#ifdef OPENMC_MPI
186
  // First do an exclusive scan to get the starting indices for
187
  start = 0;
35,095✔
188
  MPI_Exscan(&index_temp, &start, 1, MPI_INT64_T, MPI_SUM, mpi::intracomm);
35,095✔
189
  finish = start + index_temp;
35,095✔
190

191
  // Allocate space for bank_position if this hasn't been done yet
192
  int64_t bank_position[mpi::n_procs];
35,095✔
193
  MPI_Allgather(
35,095✔
194
    &start, 1, MPI_INT64_T, bank_position, 1, MPI_INT64_T, mpi::intracomm);
195
#else
196
  start = 0;
27,345✔
197
  finish = index_temp;
27,345✔
198
#endif
199

200
  // Now that the sampling is complete, we need to ensure that we have exactly
201
  // n_particles source sites. The way this is done in a reproducible manner is
202
  // to adjust only the source sites on the last processor.
203

204
  if (mpi::rank == mpi::n_procs - 1) {
62,440✔
205
    if (finish > settings::n_particles) {
47,030✔
206
      // If we have extra sites sampled, we will simply discard the extra
207
      // ones on the last processor
208
      index_temp = settings::n_particles - start;
19,193✔
209

210
    } else if (finish < settings::n_particles) {
27,837✔
211
      // If we have too few sites, repeat sites from the very end of the
212
      // fission bank
213
      sites_needed = settings::n_particles - finish;
22,817✔
214
      for (int i = 0; i < sites_needed; ++i) {
176,072✔
215
        int i_bank = simulation::fission_bank.size() - sites_needed + i;
153,255✔
216
        temp_sites[index_temp] = simulation::fission_bank[i_bank];
153,255✔
217
        ++index_temp;
153,255✔
218
      }
219
    }
220

221
    // the last processor should not be sending sites to right
222
    finish = simulation::work_index[mpi::rank + 1];
47,030✔
223
  }
224

225
  simulation::time_bank_sample.stop();
62,440✔
226
  simulation::time_bank_sendrecv.start();
62,440✔
227

228
#ifdef OPENMC_MPI
229
  // ==========================================================================
230
  // SEND BANK SITES TO NEIGHBORS
231

232
  int64_t index_local = 0;
35,095✔
233
  vector<MPI_Request> requests;
35,095✔
234

235
  if (start < settings::n_particles) {
35,095✔
236
    // Determine the index of the processor which has the first part of the
237
    // source_bank for the local processor
238
    int neighbor = upper_bound_index(
35,095✔
239
      simulation::work_index.begin(), simulation::work_index.end(), start);
35,095✔
240

241
    while (start < finish) {
58,375✔
242
      // Determine the number of sites to send
243
      int64_t n =
244
        std::min(simulation::work_index[neighbor + 1], finish) - start;
50,150✔
245

246
      // Initiate an asynchronous send of source sites to the neighboring
247
      // process
248
      if (neighbor != mpi::rank) {
50,150✔
249
        requests.emplace_back();
15,055✔
250
        MPI_Isend(&temp_sites[index_local], static_cast<int>(n),
15,055✔
251
          mpi::source_site, neighbor, mpi::rank, mpi::intracomm,
252
          &requests.back());
15,055✔
253
      }
254

255
      // Increment all indices
256
      start += n;
50,150✔
257
      index_local += n;
50,150✔
258
      ++neighbor;
50,150✔
259

260
      // Check for sites out of bounds -- this only happens in the rare
261
      // circumstance that a processor close to the end has so many sites that
262
      // it would exceed the bank on the last processor
263
      if (neighbor > mpi::n_procs - 1)
50,150✔
264
        break;
26,870✔
265
    }
266
  }
267

268
  // ==========================================================================
269
  // RECEIVE BANK SITES FROM NEIGHBORS OR TEMPORARY BANK
270

271
  start = simulation::work_index[mpi::rank];
35,095✔
272
  index_local = 0;
35,095✔
273

274
  // Determine what process has the source sites that will need to be stored at
275
  // the beginning of this processor's source bank.
276

277
  int neighbor;
278
  if (start >= bank_position[mpi::n_procs - 1]) {
35,095✔
279
    neighbor = mpi::n_procs - 1;
12,500✔
280
  } else {
281
    neighbor =
22,595✔
282
      upper_bound_index(bank_position, bank_position + mpi::n_procs, start);
22,595✔
283
  }
284

285
  while (start < simulation::work_index[mpi::rank + 1]) {
85,245✔
286
    // Determine how many sites need to be received
287
    int64_t n;
288
    if (neighbor == mpi::n_procs - 1) {
50,150✔
289
      n = simulation::work_index[mpi::rank + 1] - start;
27,555✔
290
    } else {
291
      n = std::min(bank_position[neighbor + 1],
22,595✔
292
            simulation::work_index[mpi::rank + 1]) -
45,190✔
293
          start;
294
    }
295

296
    if (neighbor != mpi::rank) {
50,150✔
297
      // If the source sites are not on this processor, initiate an
298
      // asynchronous receive for the source sites
299

300
      requests.emplace_back();
15,055✔
301
      MPI_Irecv(&simulation::source_bank[index_local], static_cast<int>(n),
15,055✔
302
        mpi::source_site, neighbor, neighbor, mpi::intracomm, &requests.back());
15,055✔
303

304
    } else {
305
      // If the source sites are on this procesor, we can simply copy them
306
      // from the temp_sites bank
307

308
      index_temp = start - bank_position[mpi::rank];
35,095✔
309
      std::copy(&temp_sites[index_temp], &temp_sites[index_temp + n],
35,095✔
310
        &simulation::source_bank[index_local]);
35,095✔
311
    }
312

313
    // Increment all indices
314
    start += n;
50,150✔
315
    index_local += n;
50,150✔
316
    ++neighbor;
50,150✔
317
  }
318

319
  // Since we initiated a series of asynchronous ISENDs and IRECVs, now we have
320
  // to ensure that the data has actually been communicated before moving on to
321
  // the next generation
322

323
  int n_request = requests.size();
35,095✔
324
  MPI_Waitall(n_request, requests.data(), MPI_STATUSES_IGNORE);
35,095✔
325

326
#else
327
  std::copy(temp_sites.data(), temp_sites.data() + settings::n_particles,
27,345✔
328
    simulation::source_bank.begin());
329
#endif
330

331
  simulation::time_bank_sendrecv.stop();
62,440✔
332
  simulation::time_bank.stop();
62,440✔
333
}
97,535✔
334

335
void calculate_average_keff()
64,480✔
336
{
337
  // Determine overall generation and number of active generations
338
  int i = overall_generation() - 1;
64,480✔
339
  int n;
340
  if (simulation::current_batch > settings::n_inactive) {
64,480✔
341
    n = settings::gen_per_batch * simulation::n_realizations +
52,893✔
342
        simulation::current_gen;
343
  } else {
344
    n = 0;
11,587✔
345
  }
346

347
  if (n <= 0) {
64,480✔
348
    // For inactive generations, use current generation k as estimate for next
349
    // generation
350
    simulation::keff = simulation::k_generation[i];
11,587✔
351
  } else {
352
    // Sample mean of keff
353
    simulation::k_sum[0] += simulation::k_generation[i];
52,893✔
354
    simulation::k_sum[1] += std::pow(simulation::k_generation[i], 2);
52,893✔
355

356
    // Determine mean
357
    simulation::keff = simulation::k_sum[0] / n;
52,893✔
358

359
    if (n > 1) {
52,893✔
360
      double t_value;
361
      if (settings::confidence_intervals) {
49,715✔
362
        // Calculate t-value for confidence intervals
363
        double alpha = 1.0 - CONFIDENCE_LEVEL;
119✔
364
        t_value = t_percentile(1.0 - alpha / 2.0, n - 1);
119✔
365
      } else {
366
        t_value = 1.0;
49,596✔
367
      }
368

369
      // Standard deviation of the sample mean of k
370
      simulation::keff_std =
49,715✔
371
        t_value *
49,715✔
372
        std::sqrt(
49,715✔
373
          (simulation::k_sum[1] / n - std::pow(simulation::keff, 2)) / (n - 1));
49,715✔
374
    }
375
  }
376
}
64,480✔
377

378
int openmc_get_keff(double* k_combined)
5,814✔
379
{
380
  k_combined[0] = 0.0;
5,814✔
381
  k_combined[1] = 0.0;
5,814✔
382

383
  // Special case for n <=3. Notice that at the end,
384
  // there is a N-3 term in a denominator.
385
  if (simulation::n_realizations <= 3 ||
5,814✔
386
      settings::solver_type == SolverType::RANDOM_RAY) {
5,598✔
387
    k_combined[0] = simulation::keff;
288✔
388
    k_combined[1] = simulation::keff_std;
288✔
389
    if (simulation::n_realizations <= 1) {
288✔
390
      k_combined[1] = std::numeric_limits<double>::infinity();
108✔
391
    }
392
    return 0;
288✔
393
  }
394

395
  // Initialize variables
396
  int64_t n = simulation::n_realizations;
5,526✔
397

398
  // Copy estimates of k-effective and its variance (not variance of the mean)
399
  const auto& gt = simulation::global_tallies;
5,526✔
400

401
  array<double, 3> kv {};
5,526✔
402
  xt::xtensor<double, 2> cov = xt::zeros<double>({3, 3});
5,526✔
403
  kv[0] = gt(GlobalTally::K_COLLISION, TallyResult::SUM) / n;
5,526✔
404
  kv[1] = gt(GlobalTally::K_ABSORPTION, TallyResult::SUM) / n;
5,526✔
405
  kv[2] = gt(GlobalTally::K_TRACKLENGTH, TallyResult::SUM) / n;
5,526✔
406
  cov(0, 0) =
11,052✔
407
    (gt(GlobalTally::K_COLLISION, TallyResult::SUM_SQ) - n * kv[0] * kv[0]) /
5,526✔
408
    (n - 1);
5,526✔
409
  cov(1, 1) =
11,052✔
410
    (gt(GlobalTally::K_ABSORPTION, TallyResult::SUM_SQ) - n * kv[1] * kv[1]) /
5,526✔
411
    (n - 1);
5,526✔
412
  cov(2, 2) =
11,052✔
413
    (gt(GlobalTally::K_TRACKLENGTH, TallyResult::SUM_SQ) - n * kv[2] * kv[2]) /
5,526✔
414
    (n - 1);
5,526✔
415

416
  // Calculate covariances based on sums with Bessel's correction
417
  cov(0, 1) = (simulation::k_col_abs - n * kv[0] * kv[1]) / (n - 1);
5,526✔
418
  cov(0, 2) = (simulation::k_col_tra - n * kv[0] * kv[2]) / (n - 1);
5,526✔
419
  cov(1, 2) = (simulation::k_abs_tra - n * kv[1] * kv[2]) / (n - 1);
5,526✔
420
  cov(1, 0) = cov(0, 1);
5,526✔
421
  cov(2, 0) = cov(0, 2);
5,526✔
422
  cov(2, 1) = cov(1, 2);
5,526✔
423

424
  // Check to see if two estimators are the same; this is guaranteed to happen
425
  // in MG-mode with survival biasing when the collision and absorption
426
  // estimators are the same, but can theoretically happen at anytime.
427
  // If it does, the standard estimators will produce floating-point
428
  // exceptions and an expression specifically derived for the combination of
429
  // two estimators (vice three) should be used instead.
430

431
  // First we will identify if there are any matching estimators
432
  int i, j;
433
  bool use_three = false;
5,526✔
434
  if ((std::abs(kv[0] - kv[1]) / kv[0] < FP_REL_PRECISION) &&
5,550✔
435
      (std::abs(cov(0, 0) - cov(1, 1)) / cov(0, 0) < FP_REL_PRECISION)) {
24✔
436
    // 0 and 1 match, so only use 0 and 2 in our comparisons
437
    i = 0;
24✔
438
    j = 2;
24✔
439

440
  } else if ((std::abs(kv[0] - kv[2]) / kv[0] < FP_REL_PRECISION) &&
5,502✔
441
             (std::abs(cov(0, 0) - cov(2, 2)) / cov(0, 0) < FP_REL_PRECISION)) {
×
442
    // 0 and 2 match, so only use 0 and 1 in our comparisons
443
    i = 0;
×
444
    j = 1;
×
445

446
  } else if ((std::abs(kv[1] - kv[2]) / kv[1] < FP_REL_PRECISION) &&
5,502✔
447
             (std::abs(cov(1, 1) - cov(2, 2)) / cov(1, 1) < FP_REL_PRECISION)) {
×
448
    // 1 and 2 match, so only use 0 and 1 in our comparisons
449
    i = 0;
×
450
    j = 1;
×
451

452
  } else {
453
    // No two estimators match, so set boolean to use all three estimators.
454
    use_three = true;
5,502✔
455
  }
456

457
  if (use_three) {
5,526✔
458
    // Use three estimators as derived in the paper by Urbatsch
459

460
    // Initialize variables
461
    double g = 0.0;
5,502✔
462
    array<double, 3> S {};
5,502✔
463

464
    for (int l = 0; l < 3; ++l) {
22,008✔
465
      // Permutations of estimates
466
      int k;
467
      switch (l) {
16,506✔
468
      case 0:
5,502✔
469
        // i = collision, j = absorption, k = tracklength
470
        i = 0;
5,502✔
471
        j = 1;
5,502✔
472
        k = 2;
5,502✔
473
        break;
5,502✔
474
      case 1:
5,502✔
475
        // i = absortion, j = tracklength, k = collision
476
        i = 1;
5,502✔
477
        j = 2;
5,502✔
478
        k = 0;
5,502✔
479
        break;
5,502✔
480
      case 2:
5,502✔
481
        // i = tracklength, j = collision, k = absorption
482
        i = 2;
5,502✔
483
        j = 0;
5,502✔
484
        k = 1;
5,502✔
485
        break;
5,502✔
486
      }
487

488
      // Calculate weighting
489
      double f = cov(j, j) * (cov(k, k) - cov(i, k)) - cov(k, k) * cov(i, j) +
16,506✔
490
                 cov(j, k) * (cov(i, j) + cov(i, k) - cov(j, k));
16,506✔
491

492
      // Add to S sums for variance of combined estimate
493
      S[0] += f * cov(0, l);
16,506✔
494
      S[1] += (cov(j, j) + cov(k, k) - 2.0 * cov(j, k)) * kv[l] * kv[l];
16,506✔
495
      S[2] += (cov(k, k) + cov(i, j) - cov(j, k) - cov(i, k)) * kv[l] * kv[j];
16,506✔
496

497
      // Add to sum for combined k-effective
498
      k_combined[0] += f * kv[l];
16,506✔
499
      g += f;
16,506✔
500
    }
501

502
    // Complete calculations of S sums
503
    for (auto& S_i : S) {
22,008✔
504
      S_i *= (n - 1);
16,506✔
505
    }
506
    S[0] *= (n - 1) * (n - 1);
5,502✔
507

508
    // Calculate combined estimate of k-effective
509
    k_combined[0] /= g;
5,502✔
510

511
    // Calculate standard deviation of combined estimate
512
    g *= (n - 1) * (n - 1);
5,502✔
513
    k_combined[1] =
5,502✔
514
      std::sqrt(S[0] / (g * n * (n - 3)) * (1 + n * ((S[1] - 2 * S[2]) / g)));
5,502✔
515

516
  } else {
517
    // Use only two estimators
518
    // These equations are derived analogously to that done in the paper by
519
    // Urbatsch, but are simpler than for the three estimators case since the
520
    // block matrices of the three estimator equations reduces to scalars here
521

522
    // Store the commonly used term
523
    double f = kv[i] - kv[j];
24✔
524
    double g = cov(i, i) + cov(j, j) - 2.0 * cov(i, j);
24✔
525

526
    // Calculate combined estimate of k-effective
527
    k_combined[0] = kv[i] - (cov(i, i) - cov(i, j)) / g * f;
24✔
528

529
    // Calculate standard deviation of combined estimate
530
    k_combined[1] = (cov(i, i) * cov(j, j) - cov(i, j) * cov(i, j)) *
24✔
531
                    (g + n * f * f) / (n * (n - 2) * g * g);
24✔
532
    k_combined[1] = std::sqrt(k_combined[1]);
24✔
533
  }
534
  return 0;
5,526✔
535
}
5,526✔
536

537
void shannon_entropy()
1,370✔
538
{
539
  // Get source weight in each mesh bin
540
  bool sites_outside;
541
  xt::xtensor<double, 1> p =
542
    simulation::entropy_mesh->count_sites(simulation::fission_bank.data(),
1,370✔
543
      simulation::fission_bank.size(), &sites_outside);
2,740✔
544

545
  // display warning message if there were sites outside entropy box
546
  if (sites_outside) {
1,370✔
547
    if (mpi::master)
×
548
      warning("Fission source site(s) outside of entropy box.");
×
549
  }
550

551
  if (mpi::master) {
1,370✔
552
    // Normalize to total weight of bank sites
553
    p /= xt::sum(p);
1,320✔
554

555
    // Sum values to obtain Shannon entropy
556
    double H = 0.0;
1,320✔
557
    for (auto p_i : p) {
241,320✔
558
      if (p_i > 0.0) {
240,000✔
559
        H -= p_i * std::log2(p_i);
148,980✔
560
      }
561
    }
562

563
    // Add value to vector
564
    simulation::entropy.push_back(H);
1,320✔
565
  }
566
}
1,370✔
567

568
void ufs_count_sites()
170✔
569
{
570
  if (simulation::current_batch == 1 && simulation::current_gen == 1) {
170✔
571
    // On the first generation, just assume that the source is already evenly
572
    // distributed so that effectively the production of fission sites is not
573
    // biased
574

575
    std::size_t n = simulation::ufs_mesh->n_bins();
17✔
576
    double vol_frac = simulation::ufs_mesh->volume_frac_;
17✔
577
    simulation::source_frac = xt::xtensor<double, 1>({n}, vol_frac);
17✔
578

579
  } else {
17✔
580
    // count number of source sites in each ufs mesh cell
581
    bool sites_outside;
582
    simulation::source_frac =
583
      simulation::ufs_mesh->count_sites(simulation::source_bank.data(),
306✔
584
        simulation::source_bank.size(), &sites_outside);
306✔
585

586
    // Check for sites outside of the mesh
587
    if (mpi::master && sites_outside) {
153✔
588
      fatal_error("Source sites outside of the UFS mesh!");
×
589
    }
590

591
#ifdef OPENMC_MPI
592
    // Send source fraction to all processors
593
    int n_bins = simulation::ufs_mesh->n_bins();
90✔
594
    MPI_Bcast(
90✔
595
      simulation::source_frac.data(), n_bins, MPI_DOUBLE, 0, mpi::intracomm);
90✔
596
#endif
597

598
    // Normalize to total weight to get fraction of source in each cell
599
    double total = xt::sum(simulation::source_frac)();
153✔
600
    simulation::source_frac /= total;
153✔
601

602
    // Since the total starting weight is not equal to n_particles, we need to
603
    // renormalize the weight of the source sites
604
    for (int i = 0; i < simulation::work_per_rank; ++i) {
108,153✔
605
      simulation::source_bank[i].wgt *= settings::n_particles / total;
108,000✔
606
    }
607
  }
608
}
170✔
609

610
double ufs_get_weight(const Particle& p)
101,952✔
611
{
612
  // Determine indices on ufs mesh for current location
613
  int mesh_bin = simulation::ufs_mesh->get_bin(p.r());
101,952✔
614
  if (mesh_bin < 0) {
101,952✔
615
    p.write_restart();
×
616
    fatal_error("Source site outside UFS mesh!");
×
617
  }
618

619
  if (simulation::source_frac(mesh_bin) != 0.0) {
101,952✔
620
    return simulation::ufs_mesh->volume_frac_ /
55,224✔
621
           simulation::source_frac(mesh_bin);
55,224✔
622
  } else {
623
    return 1.0;
46,728✔
624
  }
625
}
626

627
void write_eigenvalue_hdf5(hid_t group)
2,709✔
628
{
629
  write_dataset(group, "n_inactive", settings::n_inactive);
2,709✔
630
  write_dataset(group, "generations_per_batch", settings::gen_per_batch);
2,709✔
631
  write_dataset(group, "k_generation", simulation::k_generation);
2,709✔
632
  if (settings::entropy_on) {
2,709✔
633
    write_dataset(group, "entropy", simulation::entropy);
96✔
634
  }
635
  write_dataset(group, "k_col_abs", simulation::k_col_abs);
2,709✔
636
  write_dataset(group, "k_col_tra", simulation::k_col_tra);
2,709✔
637
  write_dataset(group, "k_abs_tra", simulation::k_abs_tra);
2,709✔
638
  array<double, 2> k_combined;
639
  openmc_get_keff(k_combined.data());
2,709✔
640
  write_dataset(group, "k_combined", k_combined);
2,709✔
641
}
2,709✔
642

643
void read_eigenvalue_hdf5(hid_t group)
46✔
644
{
645
  read_dataset(group, "generations_per_batch", settings::gen_per_batch);
46✔
646
  int n = simulation::restart_batch * settings::gen_per_batch;
46✔
647
  simulation::k_generation.resize(n);
46✔
648
  read_dataset(group, "k_generation", simulation::k_generation);
46✔
649
  if (settings::entropy_on) {
46✔
650
    read_dataset(group, "entropy", simulation::entropy);
×
651
  }
652
  read_dataset(group, "k_col_abs", simulation::k_col_abs);
46✔
653
  read_dataset(group, "k_col_tra", simulation::k_col_tra);
46✔
654
  read_dataset(group, "k_abs_tra", simulation::k_abs_tra);
46✔
655
}
46✔
656

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

© 2025 Coveralls, Inc