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

openmc-dev / openmc / 30647347479

31 Jul 2026 04:29PM UTC coverage: 81.444% (+0.04%) from 81.4%
30647347479

Pull #3934

github

web-flow
Merge 39a21605d into a8152672b
Pull Request #3934: Fix virtual surface crossing

18511 of 26803 branches covered (69.06%)

Branch coverage included in aggregate %.

25 of 25 new or added lines in 1 file covered. (100.0%)

1004 existing lines in 27 files now uncovered.

60218 of 69864 relevant lines covered (86.19%)

50326619.63 hits per line

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

91.6
/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,250,600✔
56
{
57
  // Check if energy is out of grid bounds
58
  if (E < cmfd::egrid[0]) {
2,250,600!
59
    // throw warning message
60
    warning("Detected source point below energy grid");
×
61
    return 0;
×
62
  } else if (E >= cmfd::egrid[cmfd::ng]) {
2,250,600!
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,571,800!
69
      if (E >= cmfd::egrid[g] && E < cmfd::egrid[g + 1]) {
2,571,800!
70
        return g;
2,250,600✔
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,299✔
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,299✔
87

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

92
  auto bank_size = simulation::source_bank.size();
2,299✔
93
  for (int i = 0; i < bank_size; i++) {
2,252,899✔
94
    const auto& site = simulation::source_bank[i];
2,250,600✔
95

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

99
    // if outside mesh, skip particle
100
    if (mesh_bin < 0) {
2,250,600!
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,250,600✔
107

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

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

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

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

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

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

130
  return counts;
2,299✔
131
}
2,299✔
132

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

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

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

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

157
    double norm = sourcecounts.sum() / cmfd::norm;
2,299✔
158
    for (int i = 0; i < src_size; i++) {
25,883✔
159
      if (sourcecounts[i] > 0 && cmfd_src[i] > 0) {
23,584!
160
        weightfactors[i] = cmfd_src[i] * norm / sourcecounts[i];
21,912✔
161
      }
162
    }
163
  }
164

165
  if (!feedback)
2,299✔
166
    return;
176✔
167

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

173
  // Iterate through fission bank and update particle weights
174
  for (int64_t i = 0; i < bank_size; i++) {
2,076,723✔
175
    auto& site = simulation::source_bank[i];
2,074,600✔
176
    site.wgt *= weightfactors(bank_bins(i));
2,074,600✔
177
  }
178
}
6,897✔
179

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

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

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

196
  // Set CMFD reweight properties
197
  cmfd::norm = norm;
165✔
198

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

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

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

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

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

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

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

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

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

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

247
int get_diagonal_index(int row)
44,861,674✔
248
{
249
  for (int j = cmfd::indptr[row]; j < cmfd::indptr[row + 1]; j++) {
90,362,679!
250
    if (cmfd::indices[j] == row)
90,362,679✔
251
      return j;
44,861,674✔
252
  }
253

254
  // Return -1 if not found
255
  return -1;
256
}
257

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

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

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

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

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

293
    // Copy over x vector
294
    vector<double> tmpx {x, x + cmfd::dim};
3,699,465✔
295

296
    // Perform red/black Gauss-Seidel iterations
297
    for (int irb = 0; irb < 2; irb++) {
11,098,395✔
298

299
// Loop around matrix rows
300
#pragma omp parallel for reduction(+ : err) if (cmfd::use_all_threads)
4,035,780✔
301
      for (int irow = 0; irow < cmfd::dim; irow++) {
38,940,250✔
302
        int g, i, j, k;
35,577,100✔
303
        matrix_to_indices(irow, g, i, j, k);
35,577,100✔
304

305
        // Filter out black cells
306
        if ((i + j + k) % 2 != irb)
35,577,100✔
307
          continue;
17,788,550✔
308

309
        // Get index of diagonal for current row
310
        int didx = get_diagonal_index(irow);
17,788,550✔
311

312
        // Perform temporary sums, first do left of diag, then right of diag
313
        double tmp1 = 0.0;
17,788,550✔
314
        for (int icol = cmfd::indptr[irow]; icol < didx; icol++)
33,895,525✔
315
          tmp1 += A_data[icol] * x[cmfd::indices[icol]];
16,106,975✔
316
        for (int icol = didx + 1; icol < cmfd::indptr[irow + 1]; icol++)
33,895,525✔
317
          tmp1 += A_data[icol] * x[cmfd::indices[icol]];
16,106,975✔
318

319
        // Solve for new x
320
        double x1 = (b[irow] - tmp1) / A_data[didx];
17,788,550✔
321

322
        // Perform overrelaxation
323
        x[irow] = (1.0 - w) * x[irow] + w * x1;
17,788,550✔
324

325
        // Compute residual and update error
326
        double res = (tmpx[irow] - x[irow]) / tmpx[irow];
17,788,550✔
327
        err += res * res;
17,788,550✔
328
      }
329
    }
330

331
    // Check convergence
332
    err = std::sqrt(err / cmfd::dim);
3,699,465✔
333
    if (err < tol)
3,699,465✔
334
      return igs;
47,333✔
335

336
    // Calculate new overrelaxation parameter
337
    w = 1.0 / (1.0 - 0.25 * cmfd::spectral * w);
3,652,132✔
338
  }
3,699,465✔
339

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

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

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

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

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

361
    // Copy over x vector
362
    vector<double> tmpx {x, x + cmfd::dim};
347,017✔
363

364
    // Perform red/black Gauss-Seidel iterations
365
    for (int irb = 0; irb < 2; irb++) {
1,041,051✔
366

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

373
        // Filter out black cells
374
        if ((i + j + k) % 2 != irb)
1,261,880✔
375
          continue;
630,940✔
376

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

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

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

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

408
        // Adjust with RHS vector
409
        tmp1 = b[irow] - tmp1;
630,940✔
410
        tmp2 = b[irow + 1] - tmp2;
630,940✔
411

412
        // Solve for new x
413
        double x1 = d11 * tmp1 + d12 * tmp2;
630,940✔
414
        double x2 = d21 * tmp1 + d22 * tmp2;
630,940✔
415

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

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

426
    // Check convergence
427
    err = std::sqrt(err / cmfd::dim);
347,017✔
428
    if (err < tol)
347,017✔
429
      return igs;
704✔
430

431
    // Calculate new overrelaxation parameter
432
    w = 1.0 / (1.0 - 0.25 * cmfd::spectral * w);
346,313✔
433
  }
347,017✔
434

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

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

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

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

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

456
    // Copy over x vector
457
    vector<double> tmpx {x, x + cmfd::dim};
245,894✔
458

459
    // Loop around matrix rows
460
    for (int irow = 0; irow < cmfd::dim; irow++) {
3,196,622✔
461
      // Get index of diagonal for current row
462
      int didx = get_diagonal_index(irow);
2,950,728✔
463

464
      // Perform temporary sums, first do left of diag, then right of diag
465
      double tmp1 = 0.0;
2,950,728✔
466
      for (int icol = cmfd::indptr[irow]; icol < didx; icol++)
8,852,184✔
467
        tmp1 += A_data[icol] * x[cmfd::indices[icol]];
5,901,456✔
468
      for (int icol = didx + 1; icol < cmfd::indptr[irow + 1]; icol++)
8,852,184✔
469
        tmp1 += A_data[icol] * x[cmfd::indices[icol]];
5,901,456✔
470

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

474
      // Perform overrelaxation
475
      x[irow] = (1.0 - w) * x[irow] + w * x1;
2,950,728✔
476

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

482
    // Check convergence
483
    err = std::sqrt(err / cmfd::dim);
245,894✔
484
    if (err < tol)
245,894✔
485
      return igs;
484✔
486

487
    // Calculate new overrelaxation parameter
488
    w = 1.0 / (1.0 - 0.25 * cmfd::spectral * w);
245,410✔
489
  }
245,894✔
490

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

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

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

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

511
  // Store elements of indices
512
  for (int i = 0; i < n_elements; i++)
5,313✔
513
    cmfd::indices.push_back(indices[i]);
5,148✔
514

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

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

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

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

535
extern "C" int openmc_run_linsolver(
48,521✔
536
  const double* A_data, const double* b, double* x, double tol)
537
{
538
  switch (cmfd::ng) {
48,521✔
539
  case 1:
47,333✔
540
    return cmfd_linsolver_1g(A_data, b, x, tol);
47,333✔
541
  case 2:
704✔
542
    return cmfd_linsolver_2g(A_data, b, x, tol);
704✔
543
  default:
484✔
544
    return cmfd_linsolver_ng(A_data, b, x, tol);
484✔
545
  }
546
}
547

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

555
  // Resize tensors to be empty
556
  cmfd::indexmap.resize({0});
8,362✔
557

558
  // Set pointers to null
559
  cmfd::mesh = nullptr;
8,362✔
560
}
8,362✔
561

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

© 2026 Coveralls, Inc