Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "ThermochimicaData.h"
11 : #include "ChemicalCompositionAction.h"
12 : #include "ThermochimicaUtils.h"
13 : #include "ActionWarehouse.h"
14 : #include "libmesh/int_range.h"
15 :
16 : #include <sys/mman.h> // for mmap
17 : #include <unistd.h> // for fork
18 : #include <sys/socket.h> // for socketpair
19 : #include <csignal> // for kill
20 :
21 : #ifdef THERMOCHIMICA_ENABLED
22 : #include "Thermochimica-cxx.h"
23 : #include "checkUnits.h"
24 : #endif
25 :
26 : registerMooseObject("ChemicalReactionsApp", ThermochimicaNodalData);
27 : registerMooseObject("ChemicalReactionsApp", ThermochimicaElementData);
28 :
29 : template <bool is_nodal>
30 : InputParameters
31 451 : ThermochimicaDataBase<is_nodal>::validParams()
32 : {
33 451 : InputParameters params = ThermochimicaDataBaseParent<is_nodal>::validParams();
34 :
35 : if constexpr (is_nodal)
36 328 : ThermochimicaUtils::addClassDescription(
37 : params, "Provides access to Thermochimica-calculated data at nodes.");
38 : else
39 123 : ThermochimicaUtils::addClassDescription(
40 : params, "Provides access to Thermochimica-calculated data at elements.");
41 :
42 902 : params.addRequiredCoupledVar("elements", "Amounts of elements");
43 902 : params.addRequiredCoupledVar("temperature", "Coupled temperature");
44 902 : params.addCoupledVar("pressure", 1.0, "Pressure");
45 :
46 902 : MooseEnum reinit_type("none time nodal", "nodal");
47 902 : params.addParam<MooseEnum>(
48 : "reinit_type", reinit_type, "Reinitialization scheme to use with Thermochimica");
49 :
50 902 : params.addCoupledVar("output_element_potentials", "Chemical potentials of elements");
51 902 : params.addCoupledVar("output_phases", "Amounts of phases to be output");
52 902 : params.addCoupledVar("output_species", "Amounts of species to be output");
53 902 : params.addCoupledVar("output_vapor_pressures", "Vapour pressures of species to be output");
54 902 : params.addCoupledVar("output_element_phases",
55 : "Elements whose molar amounts in specific phases are requested");
56 :
57 902 : MooseEnum mUnit_op("mole_fraction moles", "moles");
58 902 : params.addParam<MooseEnum>(
59 : "output_species_unit", mUnit_op, "Mass unit for output species: mole_fractions or moles");
60 :
61 : if constexpr (is_nodal)
62 328 : params.set<bool>("unique_node_execute") = true;
63 :
64 451 : params.addPrivateParam<ChemicalCompositionAction *>("_chemical_composition_action");
65 902 : params.addParam<FileName>("thermofile",
66 : "Thermodynamic file to be used for Thermochimica calculations");
67 451 : return params;
68 451 : }
69 :
70 : void
71 242 : ThermochimicaDataBase_handler(int /*signum*/)
72 : {
73 242 : exit(0);
74 : }
75 :
76 : template <bool is_nodal>
77 242 : ThermochimicaDataBase<is_nodal>::ThermochimicaDataBase(const InputParameters & parameters)
78 : : ThermochimicaDataBaseParent<is_nodal>(parameters),
79 242 : _pressure(coupledValue("pressure")),
80 242 : _temperature(coupledValue("temperature")),
81 242 : _n_phases(coupledComponents("output_phases")),
82 242 : _n_species(coupledComponents("output_species")),
83 242 : _n_elements(coupledComponents("elements")),
84 242 : _n_vapor_species(coupledComponents("output_vapor_pressures")),
85 242 : _n_phase_elements(coupledComponents("output_element_phases")),
86 242 : _n_potentials(coupledComponents("output_element_potentials")),
87 242 : _el(_n_elements),
88 242 : _action(*parameters.getCheckedPointerParam<ChemicalCompositionAction *>(
89 : "_chemical_composition_action")),
90 242 : _el_ids(_action.elementIDs()),
91 242 : _reinit(parameters.get<MooseEnum>("reinit_type").getEnum<ReinitializationType>()),
92 242 : _ph_names(_action.phases()),
93 242 : _element_potentials(_action.elementPotentials()),
94 242 : _species_phase_pairs(_action.speciesPhasePairs()),
95 242 : _vapor_phase_pairs(_action.vaporPhasePairs()),
96 242 : _phase_element_pairs(_action.phaseElementPairs()),
97 242 : _output_element_potentials(isCoupled("output_element_potentials")),
98 242 : _output_vapor_pressures(isCoupled("output_vapor_pressures")),
99 242 : _output_element_phases(isCoupled("output_element_phases")),
100 242 : _ph(_n_phases),
101 242 : _sp(_n_species),
102 242 : _vp(_n_vapor_species),
103 242 : _el_pot(_n_potentials),
104 242 : _el_ph(_n_phase_elements),
105 484 : _output_mass_unit(parameters.get<MooseEnum>("output_species_unit").getEnum<OutputMassUnit>())
106 : {
107 242 : ThermochimicaUtils::checkLibraryAvailability(*this);
108 :
109 242 : if (_el_ids.size() != _n_elements)
110 0 : mooseError("Element IDs size does not match number of elements.");
111 880 : for (const auto i : make_range(_n_elements))
112 638 : _el[i] = &coupledValue("elements", i);
113 :
114 484 : if (isParamValid("output_phases"))
115 : {
116 242 : if (_ph_names.size() != _n_phases)
117 0 : mooseError("Phase names vector size does not match number of phases.");
118 :
119 968 : for (const auto i : make_range(_n_phases))
120 726 : _ph[i] = &writableVariable("output_phases", i);
121 : }
122 :
123 484 : if (isParamValid("output_species"))
124 : {
125 242 : if (_species_phase_pairs.size() != _n_species)
126 0 : mooseError("Species name vector size does not match number of output species.");
127 :
128 1606 : for (const auto i : make_range(_n_species))
129 1364 : _sp[i] = &writableVariable("output_species", i);
130 : }
131 :
132 484 : if (isParamValid("output_vapor_pressures"))
133 : {
134 242 : if (_vapor_phase_pairs.size() != _n_vapor_species)
135 0 : mooseError("Vapor species name vector size does not match number of output vapor species.");
136 :
137 528 : for (const auto i : make_range(_n_vapor_species))
138 286 : _vp[i] = &writableVariable("output_vapor_pressures", i);
139 : }
140 :
141 484 : if (isParamValid("output_element_phases"))
142 : {
143 242 : if (_phase_element_pairs.size() != _n_phase_elements)
144 0 : mooseError("Element phase vector size does not match number of output elements in phases");
145 :
146 1430 : for (const auto i : make_range(_n_phase_elements))
147 1188 : _el_ph[i] = &writableVariable("output_element_phases", i);
148 : }
149 :
150 484 : if (isParamValid("output_element_potentials"))
151 : {
152 242 : if (_element_potentials.size() != _n_potentials)
153 0 : mooseError("Element potentials vector size does not match number of element potentials "
154 : "specified for output.");
155 :
156 704 : for (const auto i : make_range(_n_potentials))
157 462 : _el_pot[i] = &writableVariable("output_element_potentials", i);
158 : }
159 : // buffer size
160 : const auto dofid_size = std::max(/* send */ 1, /* receive */ 0);
161 242 : const auto real_size =
162 484 : std::max(/* send */ 2 + _n_elements,
163 242 : /* receive */ _n_phases + _n_species + _element_potentials.size() +
164 242 : _n_vapor_species + _n_phase_elements);
165 :
166 : // set up shared memory for communication with child process
167 : auto shared_mem =
168 242 : static_cast<std::byte *>(mmap(nullptr,
169 242 : dofid_size * sizeof(dof_id_type) + real_size * sizeof(Real),
170 : PROT_READ | PROT_WRITE,
171 : MAP_ANONYMOUS | MAP_SHARED,
172 : -1 /* fd */,
173 : 0 /* offset */));
174 242 : if (shared_mem == MAP_FAILED)
175 0 : mooseError("Failed to allocate shared memory for thermochimica IPC.");
176 :
177 : // set up buffer partitions
178 242 : _shared_dofid_mem = reinterpret_cast<dof_id_type *>(shared_mem);
179 242 : _shared_real_mem = reinterpret_cast<Real *>(shared_mem + dofid_size * sizeof(dof_id_type));
180 :
181 : // set up a bidirectional communication socket
182 : int sockets[2];
183 242 : if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
184 0 : mooseError("Failed to create socketpair for thermochimica IPC.");
185 :
186 : // fork child process that will manage thermochimica calls
187 242 : _pid = fork();
188 484 : if (_pid < 0)
189 0 : mooseError("Fork failed for thermochimica library.");
190 484 : if (_pid == 0)
191 : {
192 : // here we are in the child process
193 242 : _socket = sockets[0];
194 : // clean exit upon SIGTERM (mainly for Civet code coverage)
195 242 : signal(SIGTERM, ThermochimicaDataBase_handler);
196 :
197 : #ifdef THERMOCHIMICA_ENABLED
198 : // Initialize database in Thermochimica
199 484 : if (isParamValid("thermofile"))
200 : {
201 484 : const auto thermo_file = this->template getParam<FileName>("thermofile");
202 :
203 242 : if (thermo_file.length() > 1024)
204 0 : this->paramError("thermofile",
205 : "Path exceeds Thermochimica's maximal permissible length of 1024 with ",
206 : thermo_file.length(),
207 : " characters: ",
208 : thermo_file);
209 :
210 242 : Thermochimica::setThermoFilename(thermo_file);
211 :
212 : // Read in thermodynamics model, only once
213 242 : Thermochimica::parseThermoFile();
214 :
215 242 : const auto idbg = Thermochimica::checkInfoThermo();
216 242 : if (idbg != 0)
217 0 : this->paramError("thermofile", "Thermochimica data file cannot be parsed. ", idbg);
218 : }
219 : #endif
220 :
221 : while (true)
222 3104 : server();
223 : }
224 :
225 : // parent process continues here
226 242 : _socket = sockets[1];
227 242 : }
228 :
229 : template <bool is_nodal>
230 484 : ThermochimicaDataBase<is_nodal>::~ThermochimicaDataBase()
231 : {
232 242 : if (_pid)
233 242 : kill(_pid, SIGTERM);
234 968 : }
235 :
236 : template <bool is_nodal>
237 : void
238 374 : ThermochimicaDataBase<is_nodal>::initialize()
239 : {
240 374 : }
241 :
242 : template <bool is_nodal>
243 : template <typename T>
244 : void
245 5966 : ThermochimicaDataBase<is_nodal>::expect(T expect_msg)
246 : {
247 : T msg;
248 5966 : while (read(_socket, &msg, sizeof(T)) == 0)
249 : {
250 0 : if (errno == EAGAIN)
251 0 : continue;
252 0 : mooseError("Read error waiting for '", expect_msg, "' ", errno, ' ', strerror(errno));
253 : }
254 5724 : if (msg != expect_msg)
255 0 : mooseError("Expected '", expect_msg, "' but received '", msg, "'");
256 5724 : }
257 :
258 : template <bool is_nodal>
259 : template <typename T>
260 : void
261 5724 : ThermochimicaDataBase<is_nodal>::notify(T send_msg)
262 : {
263 5724 : if (write(_socket, &send_msg, sizeof(T)) != sizeof(T))
264 0 : mooseError("Failed to notify thermochimica library child process.");
265 5724 : }
266 :
267 : template <bool is_nodal>
268 : void
269 2862 : ThermochimicaDataBase<is_nodal>::execute()
270 : {
271 : // either one DOF at a node or (currently) one DOF for constant monomial FV!
272 : // This is enforced automatically by the ChemicalComposition action, which creates the correct
273 : // variables.
274 : const unsigned int qp = 0;
275 :
276 : // store current dofID
277 : if constexpr (is_nodal)
278 2052 : _shared_dofid_mem[0] = this->_current_node->id();
279 : else
280 810 : _shared_dofid_mem[0] = this->_current_elem->id();
281 :
282 : // store all required data in shared memory
283 2862 : _shared_real_mem[0] = _temperature[qp];
284 2862 : _shared_real_mem[1] = _pressure[qp];
285 9594 : for (const auto i : make_range(_n_elements))
286 6732 : _shared_real_mem[2 + i] = (*_el[i])[qp];
287 :
288 : // message child process to trigger calculation
289 2862 : notify('A');
290 :
291 : // and wait for the child process to signal end of calculation
292 2862 : expect('B');
293 :
294 : // unpack data from shared memory
295 : std::size_t idx = 0;
296 :
297 9252 : for (const auto i : make_range(_n_phases))
298 6390 : _ph[i]->setDofValue(_shared_real_mem[idx++], qp);
299 :
300 12240 : for (const auto i : make_range(_n_species))
301 9378 : _sp[i]->setDofValue(_shared_real_mem[idx++], qp);
302 :
303 2862 : if (_output_element_potentials)
304 5760 : for (const auto i : index_range(_element_potentials))
305 3996 : _el_pot[i]->setDofValue(_shared_real_mem[idx++], qp);
306 :
307 2862 : if (_output_vapor_pressures)
308 3816 : for (const auto i : make_range(_n_vapor_species))
309 2052 : _vp[i]->setDofValue(_shared_real_mem[idx++], qp);
310 :
311 2862 : if (_output_element_phases)
312 6120 : for (const auto i : make_range(_n_phase_elements))
313 4752 : _el_ph[i]->setDofValue(_shared_real_mem[idx++], qp);
314 2862 : }
315 :
316 : template <bool is_nodal>
317 : void
318 3104 : ThermochimicaDataBase<is_nodal>::server()
319 : {
320 : // wait for message from parent process
321 3104 : expect('A');
322 :
323 : #ifdef THERMOCHIMICA_ENABLED
324 : // fetch data from shared memory
325 2862 : _current_id = _shared_dofid_mem[0];
326 :
327 2862 : auto temperature = _shared_real_mem[0];
328 2862 : auto pressure = _shared_real_mem[1];
329 :
330 : // Set temperature and pressure for thermochemistry solver
331 2862 : Thermochimica::setTemperaturePressure(temperature, pressure);
332 :
333 : // Reset all element masses to 0
334 2862 : Thermochimica::setElementMass(0, 0.0);
335 :
336 : // Set element masses
337 9594 : for (const auto i : make_range(_n_elements))
338 6732 : Thermochimica::setElementMass(_el_ids[i], _shared_real_mem[2 + i]);
339 :
340 : // Optionally ask for a re-initialization (if reinit_requested == true)
341 2862 : reinitDataMooseToTc();
342 :
343 : // Calculate thermochemical equilibrium
344 2862 : Thermochimica::thermochimica();
345 :
346 : // Check for error status
347 2862 : auto idbg = Thermochimica::checkInfoThermo();
348 2862 : if (idbg != 0)
349 : // error out for now, but we should send a code to the parent process
350 0 : mooseError("Thermochimica error ", idbg);
351 :
352 : // Save data for future reinits
353 2862 : reinitDataMooseFromTc();
354 :
355 : // Get requested phase indices if phase concentration output was requested
356 : // i.e. if output_phases is coupled
357 2862 : auto moles_phase = Thermochimica::getMolesPhase();
358 :
359 : std::size_t idx = 0;
360 :
361 9252 : for (const auto i : make_range(_n_phases))
362 : {
363 : // Is this maybe constant? No it isn't for now
364 6390 : auto [index, idbg] = Thermochimica::getPhaseIndex(_ph_names[i]);
365 6390 : if (idbg != 0)
366 0 : mooseError("Failed to get index of phase '", _ph_names[i], "'");
367 : // Convert from 1-based (fortran) to 0-based (c++) indexing
368 6390 : if (index - 1 < 0)
369 2772 : _shared_real_mem[idx] = 0.0;
370 : else
371 3618 : _shared_real_mem[idx] = moles_phase[index - 1];
372 6390 : idx++;
373 : }
374 :
375 2862 : auto db_phases = Thermochimica::getPhaseNamesSystem();
376 2862 : auto getSpeciesMoles =
377 12240 : [this, moles_phase, db_phases](const std::string phase,
378 : const std::string species) -> std::pair<double, int>
379 : {
380 : Real value = 0.0;
381 : int code = 0;
382 :
383 9378 : auto [index, idbg] = Thermochimica::getPhaseIndex(phase);
384 :
385 9378 : if (Thermochimica::isPhaseMQM(
386 9378 : std::distance(db_phases.begin(), std::find(db_phases.begin(), db_phases.end(), phase))))
387 : {
388 846 : auto [fraction, idbg] = Thermochimica::getMqmqaPairMolFraction(phase, species);
389 :
390 846 : switch (_output_mass_unit)
391 : {
392 0 : case OutputMassUnit::FRACTION:
393 : {
394 : value = fraction;
395 : code = idbg;
396 0 : break;
397 : }
398 846 : case OutputMassUnit::MOLES:
399 : {
400 846 : auto [molesPair, idbgPair] = Thermochimica::getMqmqaMolesPairs(phase);
401 846 : value = molesPair * fraction;
402 846 : code = idbg + idbgPair;
403 : break;
404 : }
405 : default:
406 : break;
407 : }
408 : }
409 : else
410 : {
411 8532 : auto [fraction, idbg] = Thermochimica::getOutputMolSpeciesPhase(phase, species);
412 8532 : switch (_output_mass_unit)
413 : {
414 5328 : case OutputMassUnit::FRACTION:
415 : {
416 : value = fraction;
417 : code = idbg;
418 5328 : break;
419 : }
420 3204 : case OutputMassUnit::MOLES:
421 : {
422 3204 : value = index >= 1 ? moles_phase[index - 1] * fraction : 0.0;
423 : code = idbg;
424 : break;
425 : }
426 : default:
427 : break;
428 : }
429 : }
430 9378 : return {value, code};
431 : };
432 :
433 12240 : for (const auto i : make_range(_n_species))
434 : {
435 9378 : auto [fraction, idbg] = getSpeciesMoles(
436 9378 : _species_phase_pairs[i].first,
437 9378 : _species_phase_pairs[i].second); // can we somehow use IDs instead of strings here?
438 :
439 9378 : if (idbg == 0)
440 4302 : _shared_real_mem[idx] = fraction;
441 5076 : else if (idbg == 1)
442 5076 : _shared_real_mem[idx] = 0.0;
443 : #ifndef NDEBUG
444 : else
445 : mooseError("Failed to get phase speciation for phase '",
446 : _species_phase_pairs[i].first,
447 : "' and species '",
448 : _species_phase_pairs[i].second,
449 : "'. Thermochimica returned ",
450 : idbg);
451 : #endif
452 9378 : idx++;
453 : }
454 :
455 2862 : if (_output_element_potentials)
456 5760 : for (const auto i : index_range(_element_potentials))
457 : {
458 3996 : auto [potential, idbg] = Thermochimica::getOutputChemPot(_element_potentials[i]);
459 3996 : if (idbg == 0)
460 3960 : _shared_real_mem[idx] = potential;
461 36 : else if (idbg == 1)
462 : // element not present, just leave this at 0 for now
463 36 : _shared_real_mem[idx] = 0.0;
464 : #ifndef NDEBUG
465 : else if (idbg == -1)
466 : mooseError("Failed to get element potential for element '",
467 : _element_potentials[i],
468 : "'. Thermochimica returned ",
469 : idbg);
470 : #endif
471 3996 : idx++;
472 : }
473 :
474 2862 : if (_output_vapor_pressures)
475 3816 : for (const auto i : make_range(_n_vapor_species))
476 : {
477 2052 : auto [fraction, moles, idbg] =
478 2052 : Thermochimica::getOutputMolSpecies(_vapor_phase_pairs[i].second);
479 : libmesh_ignore(moles);
480 :
481 2052 : if (idbg == 0)
482 2016 : _shared_real_mem[idx] = fraction * pressure;
483 36 : else if (idbg == 1)
484 36 : _shared_real_mem[idx] = 0.0;
485 : #ifndef NDEBUG
486 : else
487 : mooseError("Failed to get vapor pressure for phase '",
488 : _vapor_phase_pairs[i].first,
489 : "' and species '",
490 : _vapor_phase_pairs[i].second,
491 : "'. Thermochimica returned ",
492 : idbg);
493 : #endif
494 2052 : idx++;
495 : }
496 :
497 2862 : if (_output_element_phases)
498 6120 : for (const auto i : make_range(_n_phase_elements))
499 : {
500 4752 : auto [moles, idbg] = Thermochimica::getElementMolesInPhase(_phase_element_pairs[i].second,
501 4752 : _phase_element_pairs[i].first);
502 :
503 4752 : if (idbg == 0)
504 4716 : _shared_real_mem[idx] = moles;
505 36 : else if (idbg == 1)
506 36 : _shared_real_mem[idx] = 0.0;
507 : #ifndef NDEBUG
508 : else
509 : mooseError("Failed to get moles of element '",
510 : _phase_element_pairs[i].second,
511 : "' in phase '",
512 : _phase_element_pairs[i].first,
513 : "'. Thermochimica returned ",
514 : idbg);
515 : #endif
516 4752 : idx++;
517 : }
518 : #endif
519 : // Send message back to parent process
520 2862 : notify('B');
521 2862 : }
522 :
523 : template <bool is_nodal>
524 : void
525 2862 : ThermochimicaDataBase<is_nodal>::reinitDataMooseFromTc()
526 : {
527 : #ifdef THERMOCHIMICA_ENABLED
528 2862 : auto & d = _data[_current_id];
529 :
530 2862 : if (_reinit != ReinitializationType::NONE)
531 : {
532 2466 : Thermochimica::saveReinitData();
533 2466 : auto data = Thermochimica::getReinitData();
534 :
535 2466 : if (_reinit == ReinitializationType::TIME)
536 : {
537 576 : d._assemblage = std::move(data.assemblage);
538 576 : d._moles_phase = std::move(data.molesPhase);
539 576 : d._element_potential = std::move(data.elementPotential);
540 576 : d._chemical_potential = std::move(data.chemicalPotential);
541 576 : d._mol_fraction = std::move(data.moleFraction);
542 576 : d._elements_used = std::move(data.elementsUsed);
543 576 : d._reinit_available = data.reinitAvailable;
544 : }
545 2466 : }
546 : #endif
547 2862 : }
548 :
549 : template <bool is_nodal>
550 : void
551 2862 : ThermochimicaDataBase<is_nodal>::reinitDataMooseToTc()
552 : {
553 : #ifdef THERMOCHIMICA_ENABLED
554 : // Tell Thermochimica whether a re-initialization is requested for this calculation
555 2862 : switch (_reinit)
556 : {
557 396 : case ReinitializationType::NONE:
558 396 : Thermochimica::setReinitRequested(false);
559 : break;
560 2466 : default:
561 2466 : Thermochimica::setReinitRequested(true);
562 : }
563 : // If we have re-initialization data and want a re-initialization, then
564 : // load data into Thermochimica
565 2862 : auto it = _data.find(_current_id);
566 :
567 2862 : if (it != _data.end() &&
568 1431 : _reinit == ReinitializationType::TIME) // If doing previous timestep reinit
569 : {
570 : auto & d = it->second;
571 288 : if (d._reinit_available)
572 : {
573 288 : Thermochimica::resetReinit();
574 288 : Thermochimica::ReinitializationData data;
575 288 : data.assemblage = d._assemblage;
576 288 : data.molesPhase = d._moles_phase;
577 288 : data.elementPotential = d._element_potential;
578 288 : data.chemicalPotential = d._chemical_potential;
579 288 : data.moleFraction = d._mol_fraction;
580 288 : data.elementsUsed = d._elements_used;
581 288 : Thermochimica::setReinitData(data);
582 288 : }
583 : }
584 : #endif
585 2862 : }
586 :
587 : template <bool is_nodal>
588 : const typename ThermochimicaDataBase<is_nodal>::Data &
589 0 : ThermochimicaDataBase<is_nodal>::getNodalData(dof_id_type node_id) const
590 : {
591 : if constexpr (!is_nodal)
592 0 : mooseError("Requesting nodal data from an element object.");
593 0 : return this->getData(node_id);
594 : }
595 :
596 : template <bool is_nodal>
597 : const typename ThermochimicaDataBase<is_nodal>::Data &
598 0 : ThermochimicaDataBase<is_nodal>::getElementData(dof_id_type element_id) const
599 : {
600 : if constexpr (is_nodal)
601 0 : mooseError("Requesting per element data from a nodal object.");
602 0 : return this->getData(element_id);
603 : }
604 :
605 : template <bool is_nodal>
606 : const typename ThermochimicaDataBase<is_nodal>::Data &
607 0 : ThermochimicaDataBase<is_nodal>::getData(dof_id_type id) const
608 : {
609 : const auto it = _data.find(id);
610 0 : if (it == _data.end())
611 : {
612 : if constexpr (is_nodal)
613 0 : mooseError("Unable to look up data for node ", id);
614 : else
615 0 : mooseError("Unable to look up data for element ", id);
616 : }
617 0 : return it->second;
618 : }
619 :
620 : template class ThermochimicaDataBase<true>;
621 : template class ThermochimicaDataBase<false>;
|