Line data Source code
1 : /********************************************************************/
2 : /* SOFTWARE COPYRIGHT NOTIFICATION */
3 : /* Cardinal */
4 : /* */
5 : /* (c) 2021 UChicago Argonne, LLC */
6 : /* ALL RIGHTS RESERVED */
7 : /* */
8 : /* Prepared by UChicago Argonne, LLC */
9 : /* Under Contract No. DE-AC02-06CH11357 */
10 : /* With the U. S. Department of Energy */
11 : /* */
12 : /* Prepared by Battelle Energy Alliance, LLC */
13 : /* Under Contract No. DE-AC07-05ID14517 */
14 : /* With the U. S. Department of Energy */
15 : /* */
16 : /* See LICENSE for full restrictions */
17 : /********************************************************************/
18 :
19 : #ifdef ENABLE_OPENMC_COUPLING
20 :
21 : #include "OpenMCProblemBase.h"
22 :
23 : #include "CardinalAppTypes.h"
24 : #include "AddTallyAction.h"
25 : #include "SetupMGXSAction.h"
26 :
27 : #include "OpenMCNuclideDensities.h"
28 : #include "OpenMCDomainFilterEditor.h"
29 : #include "OpenMCTallyEditor.h"
30 : #include "OpenMCCellTransform.h"
31 : #include "CriticalitySearchBase.h"
32 :
33 : #include "openmc/random_lcg.h"
34 : // For filtering \beta_eff by DNP group.
35 : #include "openmc/tallies/filter_delayedgroup.h"
36 :
37 : InputParameters
38 4317 : OpenMCProblemBase::validParams()
39 : {
40 4317 : InputParameters params = CardinalProblem::validParams();
41 8634 : params.addParam<PostprocessorName>(
42 : "power", "Power (Watts) to normalize the OpenMC tallies; only used for k-eigenvalue mode");
43 8634 : params.addParam<PostprocessorName>(
44 : "source_strength",
45 : "Neutrons/second to normalize the OpenMC tallies; only used for fixed source mode");
46 8634 : params.addParam<bool>("verbose", false, "Whether to print diagnostic information");
47 :
48 8634 : params.addParam<MooseEnum>("tally_type", getTallyTypeEnum(), "Type of tally to use in OpenMC");
49 :
50 12951 : params.addRangeCheckedParam<Real>(
51 : "scaling",
52 8634 : 1.0,
53 : "scaling > 0.0",
54 : "Scaling factor to apply to [Mesh] to get to units of centimeters that OpenMC expects; "
55 : "setting 'scaling = 100.0', for instance, indicates that the [Mesh] is in units of meters");
56 :
57 : // interfaces to directly set some OpenMC parameters
58 8634 : params.addRangeCheckedParam<unsigned int>(
59 : "openmc_verbosity",
60 : "openmc_verbosity >= 1 & openmc_verbosity <= 10",
61 : "OpenMC verbosity level; this overrides the setting in the XML files. Note that we cannot "
62 : "influence the verbosity of OpenMC's initialization routines, since these are run before "
63 : "Cardinal is initialized.");
64 8634 : params.addRangeCheckedParam<unsigned int>(
65 : "inactive_batches",
66 : "inactive_batches >= 0",
67 : "Number of inactive batches to run in OpenMC; this overrides the setting in the XML files.");
68 8634 : params.addParam<PostprocessorName>("particles",
69 : "Number of particles to run in each OpenMC batch; this "
70 : "overrides the setting in the XML files.");
71 8634 : params.addRangeCheckedParam<unsigned int>(
72 : "batches",
73 : "batches > 0",
74 : "Number of batches to run in OpenMC; this overrides the setting in the XML files.");
75 :
76 8634 : params.addParam<bool>("reuse_source",
77 8634 : false,
78 : "Whether to take the initial fission source "
79 : "for interation n to be the converged source bank from iteration n-1");
80 8634 : params.addParam<bool>(
81 : "skip_statepoint",
82 8634 : false,
83 : "Whether to skip writing any statepoint files from OpenMC; this is a performance "
84 : "optimization for scenarios where you may not want the statepoint files anyways");
85 8634 : params.addParam<bool>(
86 : "reset_seed",
87 8634 : false,
88 : "Whether to reset OpenMC's seed to the initial starting seed before each OpenMC solve");
89 :
90 : // Kinetics parameters.
91 8634 : params.addParam<bool>("calc_kinetics_params",
92 8634 : false,
93 : "Whether or not Cardinal should enable the calculation of kinetics "
94 : "parameters (Lambda effective and beta effective).");
95 8634 : params.addParam<unsigned int>(
96 : "ifp_generations",
97 : openmc::DEFAULT_IFP_N_GENERATION,
98 : "The number of generations to use with the method of iterated fission probabilities.");
99 8634 : params.addParam<FileName>(
100 : "xml_directory", "./", "The directory in which to look for OpenMC XML files.");
101 4317 : return params;
102 0 : }
103 :
104 2175 : OpenMCProblemBase::OpenMCProblemBase(const InputParameters & params)
105 : : CardinalProblem(params),
106 : PostprocessorInterface(this),
107 2175 : _verbose(getParam<bool>("verbose")),
108 4350 : _reuse_source(getParam<bool>("reuse_source")),
109 2175 : _specified_scaling(params.isParamSetByUser("scaling")),
110 4350 : _scaling(getParam<Real>("scaling")),
111 4350 : _skip_statepoint(getParam<bool>("skip_statepoint")),
112 2175 : _fixed_point_iteration(-1),
113 2175 : _total_n_particles(0),
114 2175 : _has_adaptivity(getMooseApp().actionWarehouse().hasActions("set_adaptivity_options")),
115 2175 : _run_on_adaptivity_cycle(true),
116 4350 : _calc_kinetics_params(getParam<bool>("calc_kinetics_params")),
117 4350 : _reset_seed(getParam<bool>("reset_seed")),
118 2175 : _initial_seed(openmc::openmc_get_seed()),
119 6525 : _xml_directory(getParam<FileName>("xml_directory"))
120 : {
121 4350 : if (isParamValid("tally_type"))
122 0 : mooseError("The tally system used by OpenMCProblemBase derived classes has been deprecated. "
123 : "Please add tallies with the [Tallies] block instead.");
124 :
125 : // Suppress OpenMC output when the language server is active by
126 : // decreasing the verbosity to level 1 (the lowest).
127 2175 : std::vector<std::string> argv_vec = {"openmc"};
128 8700 : if (_app.isParamValid("language_server") && _app.getParam<bool>("language_server"))
129 : {
130 0 : argv_vec.push_back("-q");
131 0 : argv_vec.push_back("1");
132 : }
133 : // Add the parameter for the XML directory at the end.
134 2175 : argv_vec.push_back(_xml_directory);
135 :
136 : std::vector<char *> argv;
137 :
138 6525 : for (const auto & arg : argv_vec)
139 : {
140 4350 : argv.push_back(const_cast<char *>(arg.data()));
141 : }
142 : // Add terminating nullptr
143 2175 : argv.push_back(nullptr);
144 :
145 2175 : openmc_init(argv.size() - 1, argv.data(), &_communicator.get());
146 :
147 : // ensure that any mapped cells have their distribcell indices generated in OpenMC
148 2175 : if (!openmc::settings::material_cell_offsets)
149 : {
150 0 : mooseWarning("Distributed properties for material cells are disabled "
151 : "in the OpenMC settings. Enabling...");
152 0 : openmc::settings::material_cell_offsets = true;
153 0 : openmc::prepare_distribcell();
154 : }
155 :
156 : // ensure that unsupported run modes are not used, while also checking for
157 : // necessary/unused input parameters for the valid run modes
158 2175 : _run_mode = openmc::settings::run_mode;
159 2175 : const auto & tally_actions = getMooseApp().actionWarehouse().getActions<AddTallyAction>();
160 2175 : const auto & mgxs_actions = getMooseApp().actionWarehouse().getActions<SetupMGXSAction>();
161 2175 : switch (_run_mode)
162 : {
163 : case openmc::RunMode::EIGENVALUE:
164 : {
165 : // Jumping through hoops to see if we're going to add tallies down the line.
166 2053 : if (tally_actions.size() > 0 || mgxs_actions.size() > 0)
167 : {
168 3368 : checkRequiredParam(params, "power", "running in k-eigenvalue mode");
169 1684 : _power = &getPostprocessorValue("power");
170 : }
171 : else
172 738 : checkUnusedParam(params, "power", "no tallies have been added");
173 :
174 4106 : checkUnusedParam(params, "source_strength", "running in k-eigenvalue mode");
175 2053 : break;
176 : }
177 : case openmc::RunMode::FIXED_SOURCE:
178 : {
179 116 : if (tally_actions.size() > 0 || mgxs_actions.size() > 0)
180 : {
181 216 : checkRequiredParam(params, "source_strength", "running in fixed source mode");
182 108 : _source_strength = &getPostprocessorValue("source_strength");
183 : }
184 : else
185 16 : checkUnusedParam(params, "source_strength", "no tallies have been added");
186 :
187 232 : checkUnusedParam(params, "inactive_batches", "running in fixed source mode");
188 232 : checkUnusedParam(params, "reuse_source", "running in fixed source mode");
189 232 : checkUnusedParam(params, "power", "running in fixed source mode");
190 116 : _reuse_source = false;
191 116 : break;
192 : }
193 6 : case openmc::RunMode::PLOTTING:
194 : case openmc::RunMode::PARTICLE:
195 : case openmc::RunMode::VOLUME:
196 6 : mooseError("Running OpenMC in plotting, particle, and volume modes is not supported through "
197 : "Cardinal! Please just run using the OpenMC executable (e.g., openmc --plot for "
198 : "plot mode).");
199 0 : default:
200 0 : mooseError("Unhandled openmc::RunMode enum in OpenMCInitAction!");
201 : }
202 :
203 2169 : _n_cell_digits = std::to_string(openmc::model::cells.size()).length();
204 :
205 2169 : if (openmc::settings::libmesh_comm)
206 0 : mooseWarning("libMesh communicator already set in OpenMC.");
207 :
208 2169 : openmc::settings::libmesh_comm = &_mesh.comm();
209 :
210 4338 : if (isParamValid("openmc_verbosity"))
211 0 : openmc::settings::verbosity = getParam<unsigned int>("openmc_verbosity");
212 :
213 4338 : if (isParamValid("inactive_batches"))
214 64 : openmc::settings::n_inactive = getParam<unsigned int>("inactive_batches");
215 :
216 4338 : if (isParamValid("particles"))
217 68 : _particles = &getPostprocessorValue("particles");
218 :
219 4338 : if (isParamValid("batches"))
220 : {
221 70 : auto xml_n_batches = openmc::settings::n_batches; // user XML setting
222 :
223 : // the getParam<unsigned int>("batches") param overrides OpenMC XML
224 : // IMPORTANT because openmc::settings:statepoint_batch is a C++ set,
225 : // we need to remove this first in the case that xml_n_batches matches
226 : // getParam<unsigned int>("batches") otherwise there will be no batch
227 : // at which Cardinal writes a statepoint
228 : openmc::settings::statepoint_batch.erase(xml_n_batches);
229 :
230 140 : int err = openmc_set_n_batches(getParam<unsigned int>("batches"),
231 : true /* set the max batches */,
232 70 : true /* add the last batch for statepoint writing */);
233 138 : catchOpenMCError(err, "set the number of batches");
234 : }
235 :
236 : // The OpenMC wrapping doesn't require material properties itself, but we might
237 : // define them on some blocks of the domain for other auxiliary kernel purposes
238 : setMaterialCoverageCheck(false);
239 :
240 : // If the user requests kinetics parameters, make sure it's enabled in OpenMC.
241 2167 : if (_calc_kinetics_params)
242 : {
243 21 : if (_run_mode != openmc::RunMode::EIGENVALUE)
244 2 : paramError("calc_kinetics_params",
245 : "Kinetic parameters can only be calculated in k-eigenvalue mode!");
246 :
247 19 : openmc::settings::ifp_on = true;
248 19 : openmc::settings::ifp_parameter = openmc::IFPParameter::Both;
249 :
250 38 : openmc::settings::ifp_n_generation = getParam<unsigned int>("ifp_generations");
251 19 : if (openmc::settings::ifp_n_generation > openmc::settings::n_inactive)
252 2 : paramError("ifp_generations",
253 : "'ifp_generations' must be less than or equal to the number of inactive batches!");
254 : }
255 2163 : }
256 :
257 1851 : OpenMCProblemBase::~OpenMCProblemBase() { openmc_finalize(); }
258 :
259 : void
260 0 : OpenMCProblemBase::fillElementalAuxVariable(const unsigned int & var_num,
261 : const std::vector<unsigned int> & elem_ids,
262 : const Real & value)
263 : {
264 0 : auto & solution = _aux->solution();
265 0 : auto sys_number = _aux->number();
266 :
267 : // loop over all the elements and set the specified variable to the specified value
268 0 : for (const auto & e : elem_ids)
269 : {
270 0 : auto elem_ptr = _mesh.queryElemPtr(e);
271 :
272 0 : if (!isLocalElem(elem_ptr))
273 0 : continue;
274 :
275 0 : auto dof_idx = elem_ptr->dof_number(sys_number, var_num, 0);
276 0 : solution.set(dof_idx, value);
277 : }
278 0 : }
279 :
280 : int
281 5050 : OpenMCProblemBase::nParticles() const
282 : {
283 5050 : return openmc::settings::n_particles;
284 : }
285 :
286 : std::string
287 7454 : OpenMCProblemBase::materialName(const int32_t index) const
288 : {
289 : // OpenMC uses -1 to indicate void materials, which don't have a name. So we return
290 : // one ourselves, or else openmc_material_get_name will throw an error.
291 7454 : if (index == -1)
292 18 : return "VOID";
293 :
294 : const char * name;
295 7436 : int err = openmc_material_get_name(index, &name);
296 7436 : catchOpenMCError(err, "get material name for material with index " + std::to_string(index));
297 :
298 7436 : std::string n = name;
299 :
300 : // if the material does not have a name, just return the ID instead
301 7436 : if (n.empty())
302 7300 : n = std::to_string(materialID(index));
303 :
304 7436 : return n;
305 : }
306 :
307 : int32_t
308 17416120 : OpenMCProblemBase::cellID(const int32_t index) const
309 : {
310 : int32_t id;
311 17416120 : int err = openmc_cell_get_id(index, &id);
312 17416120 : catchOpenMCError(err, "get ID for cell with index " + std::to_string(index));
313 17416120 : return id;
314 : }
315 :
316 : int32_t
317 2581830 : OpenMCProblemBase::materialID(const int32_t index) const
318 : {
319 2581830 : if (index == openmc::MATERIAL_VOID)
320 : return -1;
321 :
322 : int32_t id;
323 2581758 : int err = openmc_material_get_id(index, &id);
324 2581758 : catchOpenMCError(err, "get ID for material with index " + std::to_string(index));
325 2581758 : return id;
326 : }
327 :
328 : std::string
329 0 : OpenMCProblemBase::printMaterial(const int32_t & index) const
330 : {
331 0 : int32_t id = materialID(index);
332 0 : std::stringstream msg;
333 0 : msg << "material " << id;
334 0 : return msg.str();
335 0 : }
336 :
337 : std::string
338 10 : OpenMCProblemBase::printPoint(const Point & p) const
339 : {
340 10 : std::stringstream msg;
341 10 : msg << "(" << std::setprecision(6) << std::setw(7) << p(0) << ", " << std::setprecision(6)
342 10 : << std::setw(7) << p(1) << ", " << std::setprecision(6) << std::setw(7) << p(2) << ")";
343 10 : return msg.str();
344 10 : }
345 :
346 : bool
347 2572 : OpenMCProblemBase::firstSolve() const
348 : {
349 2572 : return _fixed_point_iteration < 0;
350 : }
351 :
352 : void
353 2524 : OpenMCProblemBase::externalSolve()
354 : {
355 5048 : TIME_SECTION("solveOpenMC", 1, "Solving OpenMC", false);
356 :
357 : // Check to see if this is a steady solve. If so, we can skip extra OpenMC runs
358 : // once the mesh stops getting adapted.
359 2524 : if (_has_adaptivity && !_run_on_adaptivity_cycle)
360 : {
361 18 : _console << " Skipping running OpenMC as the mesh has not changed!" << std::endl;
362 : return;
363 : }
364 :
365 2506 : _console << " Running OpenMC with " << nParticles() << " particles per batch..." << std::endl;
366 :
367 : // apply a new starting fission source
368 2506 : if (_reuse_source && !firstSolve())
369 : {
370 16 : openmc::free_memory_source();
371 16 : openmc::model::external_sources.push_back(
372 48 : std::make_unique<openmc::FileSource>(sourceBankFileName()));
373 : }
374 :
375 : // update tallies as needed before starting the OpenMC run
376 2506 : executeEditors();
377 :
378 2500 : if (_reset_seed)
379 : {
380 56 : openmc_hard_reset();
381 56 : openmc_set_seed(_initial_seed);
382 : }
383 :
384 : int err;
385 2500 : if (!firstSolve())
386 : {
387 641 : err = openmc_reset_timers();
388 641 : if (err)
389 0 : mooseError(openmc_err_msg);
390 : }
391 :
392 2500 : if (_criticality_search)
393 960 : _criticality_search->searchForCriticality([&]() { this->critSearchStep(); });
394 : else
395 : {
396 2424 : err = openmc_run();
397 2424 : if (err)
398 0 : mooseError(openmc_err_msg);
399 : }
400 :
401 2496 : _total_n_particles += nParticles();
402 :
403 2496 : _fixed_point_iteration++;
404 :
405 : // save the latest fission source for re-use in the next iteration
406 2496 : if (_reuse_source)
407 48 : writeSourceBank(sourceBankFileName());
408 2514 : }
409 :
410 : void
411 1952 : OpenMCProblemBase::initialSetup()
412 : {
413 1952 : CardinalProblem::initialSetup();
414 :
415 : // Initialize the IFP parameters tally.
416 1952 : if (_calc_kinetics_params)
417 : {
418 : // For \Lambda_eff, \beta_{eff}, and the denominator of \beta_{eff,i}
419 17 : _ifp_common_tally_index = openmc::model::tallies.size();
420 17 : _ifp_common_tally = openmc::Tally::create();
421 17 : _ifp_common_tally->set_scores({"ifp-time-numerator", "ifp-denominator", "ifp-beta-numerator"});
422 17 : _ifp_common_tally->estimator_ = openmc::TallyEstimator::COLLISION;
423 :
424 : // For \beta_{eff,i}. A separate tally is required when sieving by delayed group to compute
425 : // standard deviations and relative errors correctly for the total \beta_eff (due to covariances
426 : // between delayed groups).
427 17 : _ifp_mg_beta_tally_index = openmc::model::tallies.size();
428 17 : _ifp_mg_beta_tally = openmc::Tally::create();
429 17 : _ifp_mg_beta_tally->set_scores({"ifp-beta-numerator"});
430 17 : _ifp_mg_beta_tally->estimator_ = openmc::TallyEstimator::COLLISION;
431 :
432 : auto dnp_grp_filter =
433 17 : dynamic_cast<openmc::DelayedGroupFilter *>(openmc::Filter::create("delayedgroup"));
434 17 : std::vector<int> grps{1, 2, 3, 4, 5, 6};
435 17 : dnp_grp_filter->set_groups(openmc::span<int>(grps));
436 :
437 17 : std::vector<openmc::Filter *> df{dnp_grp_filter};
438 17 : _ifp_mg_beta_tally->set_filters({df});
439 17 : }
440 :
441 : // Find a criticality search object
442 1952 : TheWarehouse::Query query = theWarehouse().query().condition<AttribSystem>("CriticalitySearch");
443 : std::vector<CriticalitySearchBase *> objs;
444 : query.queryInto(objs);
445 :
446 1952 : if (objs.size() > 1)
447 0 : mooseError("Cannot have more than one CriticalitySearch object");
448 :
449 1952 : if (objs.size())
450 76 : _criticality_search = objs[0];
451 1952 : }
452 :
453 : void
454 5056 : OpenMCProblemBase::syncSolutions(ExternalProblem::Direction direction)
455 : {
456 : // Always run OpenMC on the first timestep in a steady solve with adaptivity. This
457 : // ensures that OpenMC runs at least once during each Picard iteration.
458 5056 : _run_on_adaptivity_cycle |= (timeStep() == 1 && !isTransient());
459 5056 : }
460 :
461 : bool
462 651 : OpenMCProblemBase::adaptMesh()
463 : {
464 651 : _run_on_adaptivity_cycle = CardinalProblem::adaptMesh() || isTransient();
465 651 : return _run_on_adaptivity_cycle;
466 : }
467 :
468 : void
469 24 : OpenMCProblemBase::writeSourceBank(const std::string & filename)
470 : {
471 24 : hid_t file_id = openmc::file_open(filename, 'w', true);
472 : openmc::write_attribute(file_id, "filetype", "source");
473 24 : openmc::write_attribute(file_id, "version", openmc::VERSION_STATEPOINT);
474 24 : openmc::write_source_bank(
475 : file_id, openmc::simulation::source_bank, openmc::simulation::work_index);
476 24 : openmc::file_close(file_id);
477 24 : }
478 :
479 : unsigned int
480 2417 : OpenMCProblemBase::numElemsInSubdomain(const SubdomainID & id) const
481 : {
482 2417 : unsigned int n = 0;
483 7076365 : for (unsigned int e = 0; e < _mesh.nElem(); ++e)
484 : {
485 7073948 : const auto * elem = _mesh.queryElemPtr(e);
486 :
487 7073948 : if (!isLocalElem(elem) || !elem->active())
488 3380064 : continue;
489 :
490 : const auto subdomain_id = elem->subdomain_id();
491 3693884 : if (id == subdomain_id)
492 1533864 : n += 1;
493 : }
494 :
495 2417 : _communicator.sum(n);
496 :
497 2417 : return n;
498 : }
499 :
500 : bool
501 22780636 : OpenMCProblemBase::isLocalElem(const Elem * elem) const
502 : {
503 22780636 : if (!elem)
504 : {
505 : // we should only not be able to find an element if the mesh is distributed
506 : libmesh_assert(!_mesh.getMesh().is_serial());
507 : return false;
508 : }
509 :
510 14562462 : if (elem->processor_id() == _communicator.rank())
511 11732228 : return true;
512 :
513 : return false;
514 : }
515 :
516 : bool
517 4 : OpenMCProblemBase::cellHasZeroInstances(const cellInfo & cell_info) const
518 : {
519 4 : auto n = openmc::model::cells.at(cell_info.first)->n_instances();
520 4 : return !n;
521 : }
522 :
523 : void
524 11393062 : OpenMCProblemBase::setCellTemperature(const int32_t & index,
525 : const int32_t & instance,
526 : const Real & T,
527 : const cellInfo & cell_info) const
528 : {
529 11393062 : int err = openmc_cell_set_temperature(index, T, &instance, false);
530 11393062 : if (err)
531 : {
532 : std::string descriptor =
533 12 : "set cell " + printCell(cell_info) + " to temperature " + Moose::stringify(T) + " (K)";
534 :
535 : // special error message if cell has zero instances
536 4 : if (cellHasZeroInstances(cell_info))
537 0 : mooseError("Failed to set the temperature for cell " + printCell(cell_info) +
538 : " with zero instances.");
539 :
540 12 : mooseError("In attempting to set cell " + printCell(cell_info) + " to temperature " +
541 4 : Moose::stringify(T) + " (K), OpenMC reported:\n\n",
542 4 : std::string(openmc_err_msg) + "\n\n" +
543 : "If you are trying to debug a model setup, you can set 'initial_properties = "
544 : "xml' to use the initial temperature and density in the OpenMC XML files for "
545 : "OpenMC's first run.");
546 : }
547 11393058 : }
548 :
549 : std::vector<int32_t>
550 442535 : OpenMCProblemBase::cellFill(const cellInfo & cell_info, int & fill_type) const
551 : {
552 442535 : int32_t * materials = nullptr;
553 442535 : int n_materials = 0;
554 :
555 442535 : int err = openmc_cell_get_fill(cell_info.first, &fill_type, &materials, &n_materials);
556 885070 : catchOpenMCError(err, "get fill of cell " + printCell(cell_info));
557 :
558 : std::vector<int32_t> material_indices;
559 442535 : material_indices.assign(materials, materials + n_materials);
560 442535 : return material_indices;
561 0 : }
562 :
563 : bool
564 442535 : OpenMCProblemBase::materialFill(const cellInfo & cell_info, int32_t & material_index) const
565 : {
566 : int fill_type;
567 442535 : auto material_indices = cellFill(cell_info, fill_type);
568 :
569 442535 : if (fill_type != static_cast<int>(openmc::Fill::MATERIAL))
570 : return false;
571 :
572 : // The number of materials in a cell is either 1, or equal to the number of instances
573 : // (if distributed materials were used).
574 442533 : if (material_indices.size() == 1)
575 439605 : material_index = material_indices[0];
576 : else
577 2928 : material_index = material_indices[cell_info.second];
578 :
579 : return true;
580 442535 : }
581 :
582 : void
583 1618 : OpenMCProblemBase::setCellDensity(const Real & density, const cellInfo & cell_info) const
584 : {
585 : // OpenMC technically allows a density of >= 0.0, but we can impose a tighter
586 : // check here with a better error message than the Excepts() in material->set_density
587 : // because it could be a very common mistake to forget to set an initial condition
588 : // for density if OpenMC runs first
589 1618 : if (density <= 0.0)
590 4 : mooseError("Densities less than or equal to zero cannot be set in the OpenMC model!\n\n cell " +
591 4 : printCell(cell_info) + " set to density " + Moose::stringify(density) + " (kg/m3)");
592 :
593 : int32_t material_index;
594 1616 : auto is_material_cell = materialFill(cell_info, material_index);
595 :
596 1616 : if (!is_material_cell)
597 0 : mooseError(
598 : "Density transfer does not currently support cells filled with universes or lattices!");
599 :
600 : // throw a special error if the cell is void, because the OpenMC error isn't very
601 : // clear what the mistake is
602 1616 : if (material_index == MATERIAL_VOID)
603 : {
604 12 : mooseWarning("Skipping setting density for cell " + printCell(cell_info) +
605 : " because this cell is void (vacuum)");
606 4 : return;
607 : }
608 :
609 : // Compute the density. We multiply density by 0.001 to convert from kg/m3
610 : // (the units assumed in the 'density' auxvariable as well as the MOOSE fluid
611 : // properties module) to g/cm3
612 1610 : int err = openmc_cell_set_density(
613 1610 : cell_info.first, _density_conversion_factor * density, &cell_info.second, false);
614 :
615 1610 : if (err)
616 : {
617 : // special error message if cell has zero instances
618 0 : if (cellHasZeroInstances(cell_info))
619 0 : mooseError("Failed to set the density for cell " + printCell(cell_info) +
620 : " with zero instances.");
621 :
622 0 : mooseError("In attempting to set cell " + printCell(cell_info) + " to density " +
623 0 : Moose::stringify(density) + " (kg/m3), OpenMC reported:\n\n",
624 0 : std::string(openmc_err_msg) + "\n\n" +
625 : "If you are trying to debug a model setup, you can set 'initial_properties = "
626 : "xml' to use the initial temperature and density in the OpenMC XML files for "
627 : "OpenMC's first run.");
628 : }
629 : }
630 :
631 : std::string
632 4541522 : OpenMCProblemBase::printCell(const cellInfo & cell_info, const bool brief) const
633 : {
634 4541522 : int32_t id = cellID(cell_info.first);
635 :
636 4541522 : std::stringstream msg;
637 4541522 : if (!brief)
638 4527875 : msg << "id ";
639 :
640 9083044 : msg << std::setw(_n_cell_digits) << Moose::stringify(id) << ", instance "
641 9083044 : << std::setw(_n_cell_digits) << Moose::stringify(cell_info.second) << " (of "
642 4541522 : << std::setw(_n_cell_digits)
643 18166088 : << Moose::stringify(openmc::model::cells.at(cell_info.first)->n_instances()) << ")";
644 :
645 4541522 : return msg.str();
646 4541522 : }
647 :
648 : void
649 2 : OpenMCProblemBase::importProperties() const
650 : {
651 2 : _console << "Reading temperature and density from properties.h5" << std::endl;
652 :
653 2 : int err = openmc_properties_import("properties.h5");
654 2 : catchOpenMCError(err, "load temperature and density from a properties.h5 file");
655 0 : }
656 :
657 : OMCTensor
658 4190 : OpenMCProblemBase::relativeError(const OMCTensor & sum,
659 : const OMCTensor & sum_sq,
660 : const int & n_realizations) const
661 : {
662 4190 : auto rel_err = openmc::tensor::zeros<double>({sum.size()});
663 :
664 478104 : for (unsigned int i = 0; i < sum.size(); ++i)
665 : {
666 473914 : auto mean = sum(i) / n_realizations;
667 473914 : auto std_dev = std::sqrt((sum_sq(i) / n_realizations - mean * mean) / (n_realizations - 1));
668 473914 : rel_err[i] = mean != 0.0 ? std_dev / std::abs(mean) : 0.0;
669 : }
670 :
671 4190 : return rel_err;
672 : }
673 :
674 : Real
675 456 : OpenMCProblemBase::relativeError(const Real & sum,
676 : const Real & sum_sq,
677 : const int & n_realizations) const
678 : {
679 456 : auto mean = sum / n_realizations;
680 456 : auto std_dev = std::sqrt((sum_sq / n_realizations - mean * mean) / (n_realizations - 1));
681 456 : return mean != 0.0 ? std_dev / std::abs(mean) : 0.0;
682 : }
683 :
684 : OMCTensor
685 5996 : OpenMCProblemBase::tallySum(const openmc::Tally * tally, const unsigned int & score) const
686 : {
687 5996 : return OMCTensor(tally->results_.slice(
688 11992 : openmc::tensor::all, score, static_cast<int>(openmc::TallyResult::SUM)));
689 : }
690 :
691 : double
692 2186 : OpenMCProblemBase::tallySumAcrossBins(std::vector<const openmc::Tally *> tally,
693 : const unsigned int & score) const
694 : {
695 : double sum = 0.0;
696 :
697 4372 : for (const auto & t : tally)
698 : {
699 2186 : auto mean = tallySum(t, score);
700 2186 : sum += mean.sum();
701 : }
702 :
703 2186 : return sum;
704 : }
705 :
706 : double
707 0 : OpenMCProblemBase::tallyMeanAcrossBins(std::vector<const openmc::Tally *> tally,
708 : const unsigned int & score) const
709 : {
710 : int n = 0;
711 0 : for (const auto & t : tally)
712 0 : n += t->n_realizations_;
713 :
714 0 : return tallySumAcrossBins(tally, score) / n;
715 : }
716 :
717 : std::string
718 2531 : OpenMCProblemBase::enumToTallyScore(const std::string & score) const
719 : {
720 : // the MultiMooseEnum is all caps, but the MooseEnum is already the correct case,
721 : // so we need to treat these as separate
722 2531 : std::string s = score;
723 2531 : if (std::all_of(
724 18731 : s.begin(), s.end(), [](unsigned char c) { return !std::isalpha(c) || std::isupper(c); }))
725 : {
726 20258 : std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
727 :
728 : // we need to revert back to some letters being uppercase for certain scores
729 2029 : if (s == "h3_production")
730 : s = "H3_production";
731 : }
732 :
733 : // MOOSE enums use underscores, OpenMC uses dashes
734 : std::replace(s.begin(), s.end(), '_', '-');
735 2531 : return s;
736 : }
737 :
738 : std::string
739 0 : OpenMCProblemBase::tallyScoreToEnum(const std::string & score) const
740 : {
741 : // MOOSE enums use underscores, OpenMC uses dashes
742 0 : std::string s = score;
743 : std::replace(s.begin(), s.end(), '-', '_');
744 0 : return s;
745 : }
746 :
747 : openmc::TallyEstimator
748 306 : OpenMCProblemBase::tallyEstimator(tally::TallyEstimatorEnum estimator) const
749 : {
750 : switch (estimator)
751 : {
752 : case tally::tracklength:
753 : return openmc::TallyEstimator::TRACKLENGTH;
754 : case tally::collision:
755 : return openmc::TallyEstimator::COLLISION;
756 : case tally::analog:
757 : return openmc::TallyEstimator::ANALOG;
758 0 : default:
759 0 : mooseError("Unhandled TallyEstimatorEnum!");
760 : }
761 : }
762 :
763 : std::string
764 0 : OpenMCProblemBase::estimatorToString(openmc::TallyEstimator estimator) const
765 : {
766 0 : switch (estimator)
767 : {
768 : case openmc::TallyEstimator::TRACKLENGTH:
769 0 : return "tracklength";
770 : case openmc::TallyEstimator::COLLISION:
771 0 : return "collision";
772 : case openmc::TallyEstimator::ANALOG:
773 0 : return "analog";
774 0 : default:
775 0 : mooseError("Unhandled TallyEstimatorEnum!");
776 : }
777 : }
778 :
779 : openmc::TriggerMetric
780 128 : OpenMCProblemBase::triggerMetric(std::string trigger) const
781 : {
782 128 : if (trigger == "variance")
783 : return openmc::TriggerMetric::variance;
784 128 : else if (trigger == "std_dev")
785 : return openmc::TriggerMetric::standard_deviation;
786 128 : else if (trigger == "rel_err")
787 : return openmc::TriggerMetric::relative_error;
788 0 : else if (trigger == "none")
789 : return openmc::TriggerMetric::not_active;
790 : else
791 0 : mooseError("Unhandled TallyTriggerTypeEnum: ", trigger);
792 : }
793 :
794 : openmc::TriggerMetric
795 2813 : OpenMCProblemBase::triggerMetric(trigger::TallyTriggerTypeEnum trigger) const
796 : {
797 : switch (trigger)
798 : {
799 : case trigger::variance:
800 : return openmc::TriggerMetric::variance;
801 : case trigger::std_dev:
802 : return openmc::TriggerMetric::standard_deviation;
803 : case trigger::rel_err:
804 : return openmc::TriggerMetric::relative_error;
805 : case trigger::none:
806 : return openmc::TriggerMetric::not_active;
807 0 : default:
808 0 : mooseError("Unhandled TallyTriggerTypeEnum!");
809 : }
810 : }
811 :
812 : bool
813 0 : OpenMCProblemBase::cellIsVoid(const cellInfo & cell_info) const
814 : {
815 : // material_index will be unchanged if the cell is filled by a universe or lattice.
816 : // Otherwise, this will get set to the material index in the cell.
817 0 : int32_t material_index = 0;
818 0 : materialFill(cell_info, material_index);
819 0 : return material_index == MATERIAL_VOID;
820 : }
821 :
822 : void
823 1100 : OpenMCProblemBase::geometryType(bool & has_csg_universe, bool & has_dag_universe) const
824 : {
825 1100 : has_csg_universe = false;
826 1100 : has_dag_universe = false;
827 :
828 : // Loop over universes and check if type is DAGMC
829 6204 : for (const auto & universe : openmc::model::universes)
830 : {
831 5104 : if (universe->geom_type() == openmc::GeometryType::DAG)
832 48 : has_dag_universe = true;
833 5056 : else if (universe->geom_type() == openmc::GeometryType::CSG)
834 5056 : has_csg_universe = true;
835 : else
836 0 : mooseError("Unhandled GeometryType enum!");
837 : }
838 1100 : }
839 :
840 : long unsigned int
841 2847 : OpenMCProblemBase::numCells() const
842 : {
843 : long unsigned int n_openmc_cells = 0;
844 94587 : for (const auto & c : openmc::model::cells)
845 91740 : n_openmc_cells += c->n_instances();
846 :
847 2847 : return n_openmc_cells;
848 : }
849 :
850 : const openmc::Tally &
851 228 : OpenMCProblemBase::getCommonKineticsTally()
852 : {
853 228 : if (!_ifp_common_tally)
854 0 : mooseError("Internal error: kinetics parameters have not been enabled.");
855 :
856 228 : return *_ifp_common_tally;
857 : }
858 :
859 : const openmc::Tally &
860 198 : OpenMCProblemBase::getMGBetaTally()
861 : {
862 198 : return *_ifp_mg_beta_tally;
863 : }
864 :
865 : bool
866 189054 : OpenMCProblemBase::isReactionRateScore(const std::string & score) const
867 : {
868 : const std::set<std::string> viable_scores = {"H3-production",
869 : "total",
870 : "absorption",
871 : "scatter",
872 : "nu-scatter",
873 : "fission",
874 : "nu-fission",
875 : "prompt-nu-fission",
876 189054 : "delayed-nu-fission"};
877 189054 : return viable_scores.count(score);
878 : }
879 :
880 : bool
881 475735 : OpenMCProblemBase::isHeatingScore(const std::string & score) const
882 : {
883 : const std::set<std::string> viable_scores = {
884 475735 : "heating", "heating-local", "kappa-fission", "fission-q-prompt", "fission-q-recoverable"};
885 475735 : return viable_scores.count(score);
886 : }
887 :
888 : unsigned int
889 9401 : OpenMCProblemBase::addExternalVariable(const std::string & name,
890 : const std::string & system,
891 : const std::vector<SubdomainName> * block)
892 : {
893 9401 : auto var_params = _factory.getValidParams("MooseVariable");
894 18802 : var_params.set<MooseEnum>("family") = "MONOMIAL";
895 18802 : var_params.set<MooseEnum>("order") = "CONSTANT";
896 :
897 9401 : if (block)
898 12018 : var_params.set<std::vector<SubdomainName>>("block") = *block;
899 :
900 9401 : checkDuplicateVariableName(name, system);
901 18794 : addAuxVariable("MooseVariable", name, var_params);
902 18794 : return _aux->getFieldVariable<Real>(0, name).number();
903 9397 : }
904 :
905 : std::string
906 5592 : OpenMCProblemBase::subdomainName(const SubdomainID & id) const
907 : {
908 5592 : std::string name = _mesh.getSubdomainName(id);
909 5592 : if (name.empty())
910 10740 : name = std::to_string(id);
911 5592 : return name;
912 : }
913 :
914 : void
915 1952 : OpenMCProblemBase::getOpenMCUserObjects()
916 : {
917 1952 : _cell_transform_uos.clear();
918 :
919 1952 : TheWarehouse::Query uo_query = theWarehouse().query().condition<AttribSystem>("UserObject");
920 : std::vector<UserObject *> userobjs;
921 : uo_query.queryInto(userobjs);
922 :
923 9635 : for (const auto & u : userobjs)
924 : {
925 7683 : OpenMCNuclideDensities * c = dynamic_cast<OpenMCNuclideDensities *>(u);
926 7683 : if (c)
927 44 : _nuclide_densities_uos.push_back(c);
928 :
929 7683 : OpenMCTallyEditor * e = dynamic_cast<OpenMCTallyEditor *>(u);
930 7683 : if (e)
931 60 : _tally_editor_uos.push_back(e);
932 :
933 7683 : OpenMCDomainFilterEditor * f = dynamic_cast<OpenMCDomainFilterEditor *>(u);
934 7683 : if (f)
935 28 : _filter_editor_uos.push_back(f);
936 :
937 7683 : OpenMCCellTransform * t = dynamic_cast<OpenMCCellTransform *>(u);
938 7683 : if (t)
939 54 : _cell_transform_uos.push_back(t);
940 : }
941 :
942 1952 : checkOpenMCUserObjectIDs();
943 1948 : }
944 :
945 : bool
946 1999 : OpenMCProblemBase::hasCellTransform() const
947 : {
948 1999 : return !_cell_transform_uos.empty();
949 : }
950 :
951 : void
952 1952 : OpenMCProblemBase::checkOpenMCUserObjectIDs() const
953 : {
954 : std::set<int32_t> tally_ids;
955 2010 : for (const auto & te : _tally_editor_uos)
956 : {
957 60 : int32_t tally_id = te->tallyId();
958 : if (tally_ids.count(tally_id) != 0)
959 2 : te->duplicateTallyError(tally_id);
960 58 : tally_ids.insert(tally_id);
961 : }
962 :
963 : std::set<int32_t> filter_ids;
964 1976 : for (const auto & fe : _filter_editor_uos)
965 : {
966 28 : int32_t filter_id = fe->filterId();
967 : if (filter_ids.count(filter_id) != 0)
968 2 : fe->duplicateFilterError(filter_id);
969 26 : filter_ids.insert(filter_id);
970 : }
971 1948 : }
972 :
973 : void
974 2118 : OpenMCProblemBase::checkTallyEditorIDs() const
975 : {
976 2118 : std::vector<int32_t> mapped_tally_ids = getMappedTallyIDs();
977 :
978 2172 : for (const auto & te : _tally_editor_uos)
979 : {
980 56 : int32_t tally_id = te->tallyId();
981 :
982 : // ensure that the TallyEditor IDs don't apply to any mapped tally objects
983 56 : if (std::find(mapped_tally_ids.begin(), mapped_tally_ids.end(), tally_id) !=
984 : mapped_tally_ids.end())
985 2 : te->mappedTallyError(tally_id);
986 : }
987 2116 : }
988 :
989 : void
990 2506 : OpenMCProblemBase::executeFilterEditors()
991 : {
992 2506 : executeControls(EXEC_FILTER_EDITORS);
993 :
994 2506 : if (!_filter_editor_uos.size())
995 : return;
996 :
997 24 : _console << "Executing filter editors..." << std::endl;
998 48 : for (const auto & fe : _filter_editor_uos)
999 24 : fe->execute();
1000 : }
1001 :
1002 : void
1003 2506 : OpenMCProblemBase::executeTallyEditors()
1004 : {
1005 2506 : executeControls(EXEC_TALLY_EDITORS);
1006 :
1007 2506 : if (!_tally_editor_uos.size())
1008 : return;
1009 :
1010 54 : _console << "Executing tally editors..." << std::endl;
1011 102 : for (const auto & te : _tally_editor_uos)
1012 54 : te->execute();
1013 : }
1014 :
1015 : void
1016 2506 : OpenMCProblemBase::executeEditors()
1017 : {
1018 2506 : executeFilterEditors();
1019 2506 : executeTallyEditors();
1020 2500 : }
1021 :
1022 : void
1023 3336 : OpenMCProblemBase::sendNuclideDensitiesToOpenMC()
1024 : {
1025 3336 : if (_nuclide_densities_uos.size() == 0)
1026 : return;
1027 :
1028 : // We could probably put this somewhere better, but it's good for now
1029 44 : executeControls(EXEC_SEND_OPENMC_DENSITIES);
1030 :
1031 44 : _console << "Sending nuclide compositions to OpenMC... ";
1032 84 : for (const auto & uo : _nuclide_densities_uos)
1033 44 : uo->setValue();
1034 : }
1035 :
1036 : #endif
|