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

openmc-dev / openmc / 22231282333

20 Feb 2026 04:04PM UTC coverage: 81.861% (-0.2%) from 82.058%
22231282333

Pull #2693

github

web-flow
Merge e5188d202 into 53ce1910f
Pull Request #2693: Add reactivity control to coupled transport-depletion analyses

17278 of 24307 branches covered (71.08%)

Branch coverage included in aggregate %.

75 of 80 new or added lines in 4 files covered. (93.75%)

3462 existing lines in 80 files now uncovered.

57630 of 67199 relevant lines covered (85.76%)

49119197.82 hits per line

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

90.67
/src/cmfd_solver.cpp
1
#include "openmc/cmfd_solver.h"
2

3
#include <cmath>
4

5
#ifdef _OPENMP
6
#include <omp.h>
7
#endif
8
#include "openmc/tensor.h"
9

10
#include "openmc/bank.h"
11
#include "openmc/capi.h"
12
#include "openmc/constants.h"
13
#include "openmc/error.h"
14
#include "openmc/mesh.h"
15
#include "openmc/message_passing.h"
16
#include "openmc/tallies/filter_energy.h"
17
#include "openmc/tallies/filter_mesh.h"
18
#include "openmc/tallies/tally.h"
19
#include "openmc/vector.h"
20

21
namespace openmc {
22

23
namespace cmfd {
24

25
//==============================================================================
26
// Global variables
27
//==============================================================================
28

29
vector<int> indptr;
30

31
vector<int> indices;
32

33
int dim;
34

35
double spectral;
36

37
int nx, ny, nz, ng;
38

39
tensor::Tensor<int> indexmap;
40

41
int use_all_threads;
42

43
StructuredMesh* mesh;
44

45
vector<double> egrid;
46

47
double norm;
48

49
} // namespace cmfd
50

51
//==============================================================================
52
// GET_CMFD_ENERGY_BIN returns the energy bin for a source site energy
53
//==============================================================================
54

55
int get_cmfd_energy_bin(const double E)
2,046,000✔
56
{
57
  // Check if energy is out of grid bounds
58
  if (E < cmfd::egrid[0]) {
2,046,000!
59
    // throw warning message
60
    warning("Detected source point below energy grid");
×
61
    return 0;
×
62
  } else if (E >= cmfd::egrid[cmfd::ng]) {
2,046,000!
63
    // throw warning message
64
    warning("Detected source point above energy grid");
×
65
    return cmfd::ng - 1;
×
66
  } else {
67
    // Iterate through energy grid to find matching bin
68
    for (int g = 0; g < cmfd::ng; g++) {
2,338,000!
69
      if (E >= cmfd::egrid[g] && E < cmfd::egrid[g + 1]) {
2,338,000!
70
        return g;
2,046,000✔
71
      }
72
    }
73
  }
74
  // Return -1 by default
75
  return -1;
×
76
}
77

78
//==============================================================================
79
// COUNT_BANK_SITES bins fission sites according to CMFD mesh and energy
80
//==============================================================================
81

82
tensor::Tensor<double> count_bank_sites(
2,090✔
83
  tensor::Tensor<int>& bins, bool* outside)
84
{
85
  // Determine shape of array for counts
86
  std::size_t cnt_size = cmfd::nx * cmfd::ny * cmfd::nz * cmfd::ng;
2,090✔
87

88
  // Create array of zeros
89
  tensor::Tensor<double> cnt = tensor::zeros<double>({cnt_size});
2,090✔
90
  bool outside_ = false;
2,090✔
91

92
  auto bank_size = simulation::source_bank.size();
2,090✔
93
  for (int i = 0; i < bank_size; i++) {
2,048,090✔
94
    const auto& site = simulation::source_bank[i];
2,046,000✔
95

96
    // determine scoring bin for CMFD mesh
97
    int mesh_bin = cmfd::mesh->get_bin(site.r);
2,046,000✔
98

99
    // if outside mesh, skip particle
100
    if (mesh_bin < 0) {
2,046,000!
UNCOV
101
      outside_ = true;
×
102
      continue;
×
103
    }
104

105
    // determine scoring bin for CMFD energy
106
    int energy_bin = get_cmfd_energy_bin(site.E);
2,046,000✔
107

108
    // add to appropriate bin
109
    cnt(mesh_bin * cmfd::ng + energy_bin) += site.wgt;
2,046,000✔
110

111
    // store bin index which is used again when updating weights
112
    bins[i] = mesh_bin * cmfd::ng + energy_bin;
2,046,000✔
113
  }
114

115
  int total = cnt.size();
2,090✔
116
  tensor::Tensor<double> counts = tensor::zeros<double>({cnt_size});
2,090✔
117

118
#ifdef OPENMC_MPI
119
  // collect values from all processors
120
  MPI_Reduce(
836✔
121
    cnt.data(), counts.data(), total, MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
836✔
122

123
  // Check if there were sites outside the mesh for any processor
124
  MPI_Reduce(&outside_, outside, 1, MPI_C_BOOL, MPI_LOR, 0, mpi::intracomm);
836✔
125

126
#else
127
  std::copy(cnt.data(), cnt.data() + total, counts.data());
1,254✔
128
  *outside = outside_;
1,254✔
129
#endif
130

131
  return counts;
4,180✔
132
}
2,090✔
133

134
//==============================================================================
135
// OPENMC_CMFD_REWEIGHT performs reweighting of particles in source bank
136
//==============================================================================
137

138
extern "C" void openmc_cmfd_reweight(
2,090✔
139
  const bool feedback, const double* cmfd_src)
140
{
141
  // Get size of source bank and cmfd_src
142
  auto bank_size = simulation::source_bank.size();
2,090✔
143
  std::size_t src_size = cmfd::nx * cmfd::ny * cmfd::nz * cmfd::ng;
2,090✔
144

145
  // count bank sites for CMFD mesh, store bins in bank_bins for reweighting
146
  tensor::Tensor<int> bank_bins = tensor::zeros<int>({bank_size});
2,090✔
147
  bool sites_outside;
148
  tensor::Tensor<double> sourcecounts =
149
    count_bank_sites(bank_bins, &sites_outside);
2,090✔
150

151
  // Compute CMFD weightfactors
152
  tensor::Tensor<double> weightfactors = tensor::ones<double>({src_size});
2,090✔
153
  if (mpi::master) {
2,090!
154
    if (sites_outside) {
2,090!
UNCOV
155
      fatal_error("Source sites outside of the CMFD mesh");
×
156
    }
157

158
    double norm = sourcecounts.sum() / cmfd::norm;
2,090✔
159
    for (int i = 0; i < src_size; i++) {
23,530✔
160
      if (sourcecounts[i] > 0 && cmfd_src[i] > 0) {
21,440!
161
        weightfactors[i] = cmfd_src[i] * norm / sourcecounts[i];
19,920✔
162
      }
163
    }
164
  }
165

166
  if (!feedback)
2,090✔
167
    return;
160✔
168

169
#ifdef OPENMC_MPI
170
  // Send weightfactors to all processors
171
  MPI_Bcast(weightfactors.data(), src_size, MPI_DOUBLE, 0, mpi::intracomm);
772✔
172
#endif
173

174
  // Iterate through fission bank and update particle weights
175
  for (int64_t i = 0; i < bank_size; i++) {
1,887,930✔
176
    auto& site = simulation::source_bank[i];
1,886,000✔
177
    site.wgt *= weightfactors(bank_bins(i));
1,886,000✔
178
  }
179
}
2,410✔
180

181
//==============================================================================
182
// OPENMC_INITIALIZE_MESH_EGRID sets the mesh and energy grid for CMFD reweight
183
//==============================================================================
184

185
extern "C" void openmc_initialize_mesh_egrid(
150✔
186
  const int meshtally_id, const int* cmfd_indices, const double norm)
187
{
188
  // Make sure all CMFD memory is freed
189
  free_memory_cmfd();
150✔
190

191
  // Set CMFD indices
192
  cmfd::nx = cmfd_indices[0];
150✔
193
  cmfd::ny = cmfd_indices[1];
150✔
194
  cmfd::nz = cmfd_indices[2];
150✔
195
  cmfd::ng = cmfd_indices[3];
150✔
196

197
  // Set CMFD reweight properties
198
  cmfd::norm = norm;
150✔
199

200
  // Find index corresponding to tally id
201
  int32_t tally_index;
202
  openmc_get_tally_index(meshtally_id, &tally_index);
150✔
203

204
  // Get filters assocaited with tally
205
  const auto& tally_filters = model::tallies[tally_index]->filters();
150✔
206

207
  // Get mesh filter index
208
  auto meshfilter_index = tally_filters[0];
150✔
209

210
  // Store energy filter index if defined, otherwise set to -1
211
  auto energy_index = (tally_filters.size() == 2) ? tally_filters[1] : -1;
150✔
212

213
  // Get mesh index from mesh filter index
214
  int32_t mesh_index;
215
  openmc_mesh_filter_get_mesh(meshfilter_index, &mesh_index);
150✔
216

217
  // Get mesh from mesh index
218
  cmfd::mesh = dynamic_cast<StructuredMesh*>(model::meshes[mesh_index].get());
150!
219

220
  // Get energy bins from energy index, otherwise use default
221
  if (energy_index != -1) {
150✔
222
    auto efilt_base = model::tally_filters[energy_index].get();
20✔
223
    auto* efilt = dynamic_cast<EnergyFilter*>(efilt_base);
20!
224
    cmfd::egrid = efilt->bins();
20✔
225
  } else {
226
    cmfd::egrid = {0.0, INFTY};
130✔
227
  }
228
}
150✔
229

230
//==============================================================================
231
// MATRIX_TO_INDICES converts a matrix index to spatial and group
232
// indices
233
//==============================================================================
234

235
void matrix_to_indices(int irow, int& g, int& i, int& j, int& k)
73,684,672✔
236
{
237
  g = irow % cmfd::ng;
73,684,672✔
238
  i = cmfd::indexmap(irow / cmfd::ng, 0);
73,684,672✔
239
  j = cmfd::indexmap(irow / cmfd::ng, 1);
73,684,672✔
240
  k = cmfd::indexmap(irow / cmfd::ng, 2);
73,684,672✔
241
}
73,684,672✔
242

243
//==============================================================================
244
// GET_DIAGONAL_INDEX returns the index in CSR index array corresponding to
245
// the diagonal element of a specified row
246
//==============================================================================
247

248
int get_diagonal_index(int row)
40,786,372✔
249
{
250
  for (int j = cmfd::indptr[row]; j < cmfd::indptr[row + 1]; j++) {
82,153,262!
251
    if (cmfd::indices[j] == row)
82,153,262✔
252
      return j;
40,786,372✔
253
  }
254

255
  // Return -1 if not found
UNCOV
256
  return -1;
×
257
}
258

259
//==============================================================================
260
// SET_INDEXMAP sets the elements of indexmap based on input coremap
261
//==============================================================================
262

263
void set_indexmap(const int* coremap)
140✔
264
{
265
  for (int z = 0; z < cmfd::nz; z++) {
280✔
266
    for (int y = 0; y < cmfd::ny; y++) {
290✔
267
      for (int x = 0; x < cmfd::nx; x++) {
1,530✔
268
        int idx = (z * cmfd::ny * cmfd::nx) + (y * cmfd::nx) + x;
1,380✔
269
        if (coremap[idx] != CMFD_NOACCEL) {
1,380!
270
          int counter = coremap[idx];
1,380✔
271
          cmfd::indexmap(counter, 0) = x;
1,380✔
272
          cmfd::indexmap(counter, 1) = y;
1,380✔
273
          cmfd::indexmap(counter, 2) = z;
1,380✔
274
        }
275
      }
276
    }
277
  }
278
}
140✔
279

280
//==============================================================================
281
// CMFD_LINSOLVER_1G solves a one group CMFD linear system
282
//==============================================================================
283

284
int cmfd_linsolver_1g(
43,030✔
285
  const double* A_data, const double* b, double* x, double tol)
286
{
287
  // Set overrelaxation parameter
288
  double w = 1.0;
43,030✔
289

290
  // Perform Gauss-Seidel iterations
291
  for (int igs = 1; igs <= 10000; igs++) {
3,363,518!
292
    double err = 0.0;
3,363,518✔
293

294
    // Copy over x vector
295
    vector<double> tmpx {x, x + cmfd::dim};
3,363,518✔
296

297
    // Perform red/black Gauss-Seidel iterations
298
    for (int irb = 0; irb < 2; irb++) {
10,090,554✔
299

300
// Loop around matrix rows
301
#pragma omp parallel for reduction(+ : err) if (cmfd::use_all_threads)
4,036,516✔
302
      for (int irow = 0; irow < cmfd::dim; irow++) {
31,152,200✔
303
        int g, i, j, k;
304
        matrix_to_indices(irow, g, i, j, k);
28,461,680✔
305

306
        // Filter out black cells
307
        if ((i + j + k) % 2 != irb)
28,461,680✔
308
          continue;
14,230,840✔
309

310
        // Get index of diagonal for current row
311
        int didx = get_diagonal_index(irow);
14,230,840✔
312

313
        // Perform temporary sums, first do left of diag, then right of diag
314
        double tmp1 = 0.0;
14,230,840✔
315
        for (int icol = cmfd::indptr[irow]; icol < didx; icol++)
27,116,420✔
316
          tmp1 += A_data[icol] * x[cmfd::indices[icol]];
12,885,580✔
317
        for (int icol = didx + 1; icol < cmfd::indptr[irow + 1]; icol++)
27,116,420✔
318
          tmp1 += A_data[icol] * x[cmfd::indices[icol]];
12,885,580✔
319

320
        // Solve for new x
321
        double x1 = (b[irow] - tmp1) / A_data[didx];
14,230,840✔
322

323
        // Perform overrelaxation
324
        x[irow] = (1.0 - w) * x[irow] + w * x1;
14,230,840✔
325

326
        // Compute residual and update error
327
        double res = (tmpx[irow] - x[irow]) / tmpx[irow];
14,230,840✔
328
        err += res * res;
14,230,840✔
329
      }
330
    }
331

332
    // Check convergence
333
    err = std::sqrt(err / cmfd::dim);
3,363,518✔
334
    if (err < tol)
3,363,518✔
335
      return igs;
43,030✔
336

337
    // Calculate new overrelaxation parameter
338
    w = 1.0 / (1.0 - 0.25 * cmfd::spectral * w);
3,320,488✔
339
  }
3,363,518✔
340

341
  // Throw error, as max iterations met
UNCOV
342
  fatal_error("Maximum Gauss-Seidel iterations encountered.");
×
343

344
  // Return -1 by default, although error thrown before reaching this point
345
  return -1;
346
}
347

348
//==============================================================================
349
// CMFD_LINSOLVER_2G solves a two group CMFD linear system
350
//==============================================================================
351

352
int cmfd_linsolver_2g(
640✔
353
  const double* A_data, const double* b, double* x, double tol)
354
{
355
  // Set overrelaxation parameter
356
  double w = 1.0;
640✔
357

358
  // Perform Gauss-Seidel iterations
359
  for (int igs = 1; igs <= 10000; igs++) {
315,311!
360
    double err = 0.0;
315,311✔
361

362
    // Copy over x vector
363
    vector<double> tmpx {x, x + cmfd::dim};
315,311✔
364

365
    // Perform red/black Gauss-Seidel iterations
366
    for (int irb = 0; irb < 2; irb++) {
945,933✔
367

368
// Loop around matrix rows
369
#pragma omp parallel for reduction(+ : err) if (cmfd::use_all_threads)
378,246✔
370
      for (int irow = 0; irow < cmfd::dim; irow += 2) {
1,261,880✔
371
        int g, i, j, k;
372
        matrix_to_indices(irow, g, i, j, k);
1,009,504✔
373

374
        // Filter out black cells
375
        if ((i + j + k) % 2 != irb)
1,009,504✔
376
          continue;
504,752✔
377

378
        // Get index of diagonals for current row and next row
379
        int d1idx = get_diagonal_index(irow);
504,752✔
380
        int d2idx = get_diagonal_index(irow + 1);
504,752✔
381

382
        // Get block diagonal
383
        double m11 = A_data[d1idx]; // group 1 diagonal
504,752✔
384
        double m12 =
504,752✔
385
          A_data[d1idx + 1]; // group 1 right of diagonal (sorted by col)
504,752✔
386
        double m21 =
504,752✔
387
          A_data[d2idx - 1];        // group 2 left of diagonal (sorted by col)
504,752✔
388
        double m22 = A_data[d2idx]; // group 2 diagonal
504,752✔
389

390
        // Analytically invert the diagonal
391
        double dm = m11 * m22 - m12 * m21;
504,752✔
392
        double d11 = m22 / dm;
504,752✔
393
        double d12 = -m12 / dm;
504,752✔
394
        double d21 = -m21 / dm;
504,752✔
395
        double d22 = m11 / dm;
504,752✔
396

397
        // Perform temporary sums, first do left of diag, then right of diag
398
        double tmp1 = 0.0;
504,752✔
399
        double tmp2 = 0.0;
504,752✔
400
        for (int icol = cmfd::indptr[irow]; icol < d1idx; icol++)
1,009,504✔
401
          tmp1 += A_data[icol] * x[cmfd::indices[icol]];
504,752✔
402
        for (int icol = cmfd::indptr[irow + 1]; icol < d2idx - 1; icol++)
1,009,504✔
403
          tmp2 += A_data[icol] * x[cmfd::indices[icol]];
504,752✔
404
        for (int icol = d1idx + 2; icol < cmfd::indptr[irow + 1]; icol++)
1,009,504✔
405
          tmp1 += A_data[icol] * x[cmfd::indices[icol]];
504,752✔
406
        for (int icol = d2idx + 1; icol < cmfd::indptr[irow + 2]; icol++)
1,009,504✔
407
          tmp2 += A_data[icol] * x[cmfd::indices[icol]];
504,752✔
408

409
        // Adjust with RHS vector
410
        tmp1 = b[irow] - tmp1;
504,752✔
411
        tmp2 = b[irow + 1] - tmp2;
504,752✔
412

413
        // Solve for new x
414
        double x1 = d11 * tmp1 + d12 * tmp2;
504,752✔
415
        double x2 = d21 * tmp1 + d22 * tmp2;
504,752✔
416

417
        // Perform overrelaxation
418
        x[irow] = (1.0 - w) * x[irow] + w * x1;
504,752✔
419
        x[irow + 1] = (1.0 - w) * x[irow + 1] + w * x2;
504,752✔
420

421
        // Compute residual and update error
422
        double res = (tmpx[irow] - x[irow]) / tmpx[irow];
504,752✔
423
        err += res * res;
504,752✔
424
      }
425
    }
426

427
    // Check convergence
428
    err = std::sqrt(err / cmfd::dim);
315,311✔
429
    if (err < tol)
315,311✔
430
      return igs;
640✔
431

432
    // Calculate new overrelaxation parameter
433
    w = 1.0 / (1.0 - 0.25 * cmfd::spectral * w);
314,671✔
434
  }
315,311✔
435

436
  // Throw error, as max iterations met
UNCOV
437
  fatal_error("Maximum Gauss-Seidel iterations encountered.");
×
438

439
  // Return -1 by default, although error thrown before reaching this point
440
  return -1;
441
}
442

443
//==============================================================================
444
// CMFD_LINSOLVER_NG solves a general CMFD linear system
445
//==============================================================================
446

447
int cmfd_linsolver_ng(
440✔
448
  const double* A_data, const double* b, double* x, double tol)
449
{
450
  // Set overrelaxation parameter
451
  double w = 1.0;
440✔
452

453
  // Perform Gauss-Seidel iterations
454
  for (int igs = 1; igs <= 10000; igs++) {
223,566!
455
    double err = 0.0;
223,566✔
456

457
    // Copy over x vector
458
    vector<double> tmpx {x, x + cmfd::dim};
223,566✔
459

460
    // Loop around matrix rows
461
    for (int irow = 0; irow < cmfd::dim; irow++) {
2,906,358✔
462
      // Get index of diagonal for current row
463
      int didx = get_diagonal_index(irow);
2,682,792✔
464

465
      // Perform temporary sums, first do left of diag, then right of diag
466
      double tmp1 = 0.0;
2,682,792✔
467
      for (int icol = cmfd::indptr[irow]; icol < didx; icol++)
8,048,376✔
468
        tmp1 += A_data[icol] * x[cmfd::indices[icol]];
5,365,584✔
469
      for (int icol = didx + 1; icol < cmfd::indptr[irow + 1]; icol++)
8,048,376✔
470
        tmp1 += A_data[icol] * x[cmfd::indices[icol]];
5,365,584✔
471

472
      // Solve for new x
473
      double x1 = (b[irow] - tmp1) / A_data[didx];
2,682,792✔
474

475
      // Perform overrelaxation
476
      x[irow] = (1.0 - w) * x[irow] + w * x1;
2,682,792✔
477

478
      // Compute residual and update error
479
      double res = (tmpx[irow] - x[irow]) / tmpx[irow];
2,682,792✔
480
      err += res * res;
2,682,792✔
481
    }
482

483
    // Check convergence
484
    err = std::sqrt(err / cmfd::dim);
223,566✔
485
    if (err < tol)
223,566✔
486
      return igs;
440✔
487

488
    // Calculate new overrelaxation parameter
489
    w = 1.0 / (1.0 - 0.25 * cmfd::spectral * w);
223,126✔
490
  }
223,566✔
491

492
  // Throw error, as max iterations met
UNCOV
493
  fatal_error("Maximum Gauss-Seidel iterations encountered.");
×
494

495
  // Return -1 by default, although error thrown before reaching this point
496
  return -1;
497
}
498

499
//==============================================================================
500
// OPENMC_INITIALIZE_LINSOLVER sets the fixed variables that are used for the
501
// linear solver
502
//==============================================================================
503

504
extern "C" void openmc_initialize_linsolver(const int* indptr, int len_indptr,
150✔
505
  const int* indices, int n_elements, int dim, double spectral, const int* map,
506
  bool use_all_threads)
507
{
508
  // Store elements of indptr
509
  for (int i = 0; i < len_indptr; i++)
1,840✔
510
    cmfd::indptr.push_back(indptr[i]);
1,690✔
511

512
  // Store elements of indices
513
  for (int i = 0; i < n_elements; i++)
4,830✔
514
    cmfd::indices.push_back(indices[i]);
4,680✔
515

516
  // Set dimenion of CMFD problem and specral radius
517
  cmfd::dim = dim;
150✔
518
  cmfd::spectral = spectral;
150✔
519

520
  // Set indexmap if 1 or 2 group problem
521
  if (cmfd::ng == 1 || cmfd::ng == 2) {
150✔
522
    // Resize indexmap and set its elements
523
    cmfd::indexmap.resize({static_cast<size_t>(dim), 3});
140✔
524
    set_indexmap(map);
140✔
525
  }
526

527
  // Use all threads allocated to OpenMC simulation to run CMFD solver
528
  cmfd::use_all_threads = use_all_threads;
150✔
529
}
150✔
530

531
//==============================================================================
532
// OPENMC_RUN_LINSOLVER runs a Gauss Seidel linear solver to solve CMFD matrix
533
// equations
534
//==============================================================================
535

536
extern "C" int openmc_run_linsolver(
44,110✔
537
  const double* A_data, const double* b, double* x, double tol)
538
{
539
  switch (cmfd::ng) {
44,110✔
540
  case 1:
43,030✔
541
    return cmfd_linsolver_1g(A_data, b, x, tol);
43,030✔
542
  case 2:
640✔
543
    return cmfd_linsolver_2g(A_data, b, x, tol);
640✔
544
  default:
440✔
545
    return cmfd_linsolver_ng(A_data, b, x, tol);
440✔
546
  }
547
}
548

549
void free_memory_cmfd()
6,804✔
550
{
551
  // Clear vectors
552
  cmfd::indptr.clear();
6,804✔
553
  cmfd::indices.clear();
6,804✔
554
  cmfd::egrid.clear();
6,804✔
555

556
  // Resize tensors to be empty
557
  cmfd::indexmap.resize({0});
6,804✔
558

559
  // Set pointers to null
560
  cmfd::mesh = nullptr;
6,804✔
561
}
6,804✔
562

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