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 209 : ThermochimicaDataBase<is_nodal>::validParams()
32 : {
33 209 : InputParameters params = ThermochimicaDataBaseParent<is_nodal>::validParams();
34 :
35 : if constexpr (is_nodal)
36 152 : ThermochimicaUtils::addClassDescription(
37 : params, "Provides access to Thermochimica-calculated data at nodes.");
38 : else
39 57 : ThermochimicaUtils::addClassDescription(
40 : params, "Provides access to Thermochimica-calculated data at elements.");
41 :
42 418 : params.addRequiredCoupledVar("elements", "Amounts of elements");
43 418 : params.addRequiredCoupledVar("temperature", "Coupled temperature");
44 418 : params.addCoupledVar("pressure", 1.0, "Pressure");
45 :
46 418 : MooseEnum reinit_type("none time nodal", "nodal");
47 418 : params.addParam<MooseEnum>(
48 : "reinit_type", reinit_type, "Reinitialization scheme to use with Thermochimica");
49 :
50 418 : params.addCoupledVar("output_element_potentials", "Chemical potentials of elements");
51 418 : params.addCoupledVar("output_phases", "Amounts of phases to be output");
52 418 : params.addCoupledVar("output_species", "Amounts of species to be output");
53 418 : params.addCoupledVar("output_vapor_pressures", "Vapour pressures of species to be output");
54 418 : params.addCoupledVar("output_element_phases",
55 : "Elements whose molar amounts in specific phases are requested");
56 :
57 418 : MooseEnum mUnit_op("mole_fraction moles", "moles");
58 418 : 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 152 : params.set<bool>("unique_node_execute") = true;
63 :
64 209 : params.addPrivateParam<ChemicalCompositionAction *>("_chemical_composition_action");
65 418 : params.addParam<FileName>("thermofile",
66 : "Thermodynamic file to be used for Thermochimica calculations");
67 209 : return params;
68 209 : }
69 :
70 : void
71 110 : ThermochimicaDataBase_handler(int /*signum*/)
72 : {
73 110 : exit(0);
74 : }
75 :
76 : template <bool is_nodal>
77 110 : ThermochimicaDataBase<is_nodal>::ThermochimicaDataBase(const InputParameters & parameters)
78 : : ThermochimicaDataBaseParent<is_nodal>(parameters),
79 110 : _pressure(coupledValue("pressure")),
80 110 : _temperature(coupledValue("temperature")),
81 110 : _n_phases(coupledComponents("output_phases")),
82 110 : _n_species(coupledComponents("output_species")),
83 110 : _n_elements(coupledComponents("elements")),
84 110 : _n_vapor_species(coupledComponents("output_vapor_pressures")),
85 110 : _n_phase_elements(coupledComponents("output_element_phases")),
86 110 : _n_potentials(coupledComponents("output_element_potentials")),
87 110 : _el(_n_elements),
88 110 : _action(*parameters.getCheckedPointerParam<ChemicalCompositionAction *>(
89 : "_chemical_composition_action")),
90 110 : _el_ids(_action.elementIDs()),
91 110 : _reinit(parameters.get<MooseEnum>("reinit_type").getEnum<ReinitializationType>()),
92 110 : _ph_names(_action.phases()),
93 110 : _element_potentials(_action.elementPotentials()),
94 110 : _species_phase_pairs(_action.speciesPhasePairs()),
95 110 : _vapor_phase_pairs(_action.vaporPhasePairs()),
96 110 : _phase_element_pairs(_action.phaseElementPairs()),
97 110 : _output_element_potentials(isCoupled("output_element_potentials")),
98 110 : _output_vapor_pressures(isCoupled("output_vapor_pressures")),
99 110 : _output_element_phases(isCoupled("output_element_phases")),
100 110 : _ph(_n_phases),
101 110 : _sp(_n_species),
102 110 : _vp(_n_vapor_species),
103 110 : _el_pot(_n_potentials),
104 110 : _el_ph(_n_phase_elements),
105 220 : _output_mass_unit(parameters.get<MooseEnum>("output_species_unit").getEnum<OutputMassUnit>())
106 : {
107 110 : ThermochimicaUtils::checkLibraryAvailability(*this);
108 :
109 110 : if (_el_ids.size() != _n_elements)
110 0 : mooseError("Element IDs size does not match number of elements.");
111 400 : for (const auto i : make_range(_n_elements))
112 290 : _el[i] = &coupledValue("elements", i);
113 :
114 220 : if (isParamValid("output_phases"))
115 : {
116 110 : if (_ph_names.size() != _n_phases)
117 0 : mooseError("Phase names vector size does not match number of phases.");
118 :
119 440 : for (const auto i : make_range(_n_phases))
120 330 : _ph[i] = &writableVariable("output_phases", i);
121 : }
122 :
123 220 : if (isParamValid("output_species"))
124 : {
125 110 : if (_species_phase_pairs.size() != _n_species)
126 0 : mooseError("Species name vector size does not match number of output species.");
127 :
128 730 : for (const auto i : make_range(_n_species))
129 620 : _sp[i] = &writableVariable("output_species", i);
130 : }
131 :
132 220 : if (isParamValid("output_vapor_pressures"))
133 : {
134 110 : 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 240 : for (const auto i : make_range(_n_vapor_species))
138 130 : _vp[i] = &writableVariable("output_vapor_pressures", i);
139 : }
140 :
141 220 : if (isParamValid("output_element_phases"))
142 : {
143 110 : 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 650 : for (const auto i : make_range(_n_phase_elements))
147 540 : _el_ph[i] = &writableVariable("output_element_phases", i);
148 : }
149 :
150 220 : if (isParamValid("output_element_potentials"))
151 : {
152 110 : 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 320 : for (const auto i : make_range(_n_potentials))
157 210 : _el_pot[i] = &writableVariable("output_element_potentials", i);
158 : }
159 : // buffer size
160 : const auto dofid_size = std::max(/* send */ 1, /* receive */ 0);
161 110 : const auto real_size =
162 220 : std::max(/* send */ 2 + _n_elements,
163 110 : /* receive */ _n_phases + _n_species + _element_potentials.size() +
164 110 : _n_vapor_species + _n_phase_elements);
165 :
166 : // set up shared memory for communication with child process
167 : auto shared_mem =
168 110 : static_cast<std::byte *>(mmap(nullptr,
169 110 : 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 110 : if (shared_mem == MAP_FAILED)
175 0 : mooseError("Failed to allocate shared memory for thermochimica IPC.");
176 :
177 : // set up buffer partitions
178 110 : _shared_dofid_mem = reinterpret_cast<dof_id_type *>(shared_mem);
179 110 : _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 110 : 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 110 : _pid = fork();
188 220 : if (_pid < 0)
189 0 : mooseError("Fork failed for thermochimica library.");
190 220 : if (_pid == 0)
191 : {
192 : // here we are in the child process
193 110 : _socket = sockets[0];
194 : // clean exit upon SIGTERM (mainly for Civet code coverage)
195 110 : signal(SIGTERM, ThermochimicaDataBase_handler);
196 :
197 : #ifdef THERMOCHIMICA_ENABLED
198 : // Initialize database in Thermochimica
199 220 : if (isParamValid("thermofile"))
200 : {
201 220 : const auto thermo_file = this->template getParam<FileName>("thermofile");
202 :
203 110 : 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 110 : Thermochimica::setThermoFilename(thermo_file);
211 :
212 : // Read in thermodynamics model, only once
213 110 : Thermochimica::parseThermoFile();
214 :
215 110 : const auto idbg = Thermochimica::checkInfoThermo();
216 110 : if (idbg != 0)
217 0 : this->paramError("thermofile", "Thermochimica data file cannot be parsed. ", idbg);
218 : }
219 : #endif
220 :
221 : while (true)
222 2018 : server();
223 : }
224 :
225 : // parent process continues here
226 110 : _socket = sockets[1];
227 110 : }
228 :
229 : template <bool is_nodal>
230 110 : ThermochimicaDataBase<is_nodal>::~ThermochimicaDataBase()
231 : {
232 110 : if (_pid)
233 110 : kill(_pid, SIGTERM);
234 330 : }
235 :
236 : template <bool is_nodal>
237 : void
238 198 : ThermochimicaDataBase<is_nodal>::initialize()
239 : {
240 198 : }
241 :
242 : template <bool is_nodal>
243 : template <typename T>
244 : void
245 3926 : ThermochimicaDataBase<is_nodal>::expect(T expect_msg)
246 : {
247 : T msg;
248 3926 : 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 3816 : if (msg != expect_msg)
255 0 : mooseError("Expected '", expect_msg, "' but received '", msg, "'");
256 3816 : }
257 :
258 : template <bool is_nodal>
259 : template <typename T>
260 : void
261 3816 : ThermochimicaDataBase<is_nodal>::notify(T send_msg)
262 : {
263 3816 : if (write(_socket, &send_msg, sizeof(T)) != sizeof(T))
264 0 : mooseError("Failed to notify thermochimica library child process.");
265 3816 : }
266 :
267 : template <bool is_nodal>
268 : void
269 1908 : 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 1368 : _shared_dofid_mem[0] = this->_current_node->id();
279 : else
280 540 : _shared_dofid_mem[0] = this->_current_elem->id();
281 :
282 : // store all required data in shared memory
283 1908 : _shared_real_mem[0] = _temperature[qp];
284 1908 : _shared_real_mem[1] = _pressure[qp];
285 6396 : for (const auto i : make_range(_n_elements))
286 4488 : _shared_real_mem[2 + i] = (*_el[i])[qp];
287 :
288 : // message child process to trigger calculation
289 1908 : notify('A');
290 :
291 : // and wait for the child process to signal end of calculation
292 1908 : expect('B');
293 :
294 : // unpack data from shared memory
295 : std::size_t idx = 0;
296 :
297 6168 : for (const auto i : make_range(_n_phases))
298 4260 : _ph[i]->setDofValue(_shared_real_mem[idx++], qp);
299 :
300 8160 : for (const auto i : make_range(_n_species))
301 6252 : _sp[i]->setDofValue(_shared_real_mem[idx++], qp);
302 :
303 1908 : if (_output_element_potentials)
304 3840 : for (const auto i : index_range(_element_potentials))
305 2664 : _el_pot[i]->setDofValue(_shared_real_mem[idx++], qp);
306 :
307 1908 : if (_output_vapor_pressures)
308 2544 : for (const auto i : make_range(_n_vapor_species))
309 1368 : _vp[i]->setDofValue(_shared_real_mem[idx++], qp);
310 :
311 1908 : if (_output_element_phases)
312 4080 : for (const auto i : make_range(_n_phase_elements))
313 3168 : _el_ph[i]->setDofValue(_shared_real_mem[idx++], qp);
314 1908 : }
315 :
316 : template <bool is_nodal>
317 : void
318 2018 : ThermochimicaDataBase<is_nodal>::server()
319 : {
320 : // wait for message from parent process
321 2018 : expect('A');
322 :
323 : #ifdef THERMOCHIMICA_ENABLED
324 : // fetch data from shared memory
325 1908 : _current_id = _shared_dofid_mem[0];
326 :
327 1908 : auto temperature = _shared_real_mem[0];
328 1908 : auto pressure = _shared_real_mem[1];
329 :
330 : // Set temperature and pressure for thermochemistry solver
331 1908 : Thermochimica::setTemperaturePressure(temperature, pressure);
332 :
333 : // Reset all element masses to 0
334 1908 : Thermochimica::setElementMass(0, 0.0);
335 :
336 : // Set element masses
337 6396 : for (const auto i : make_range(_n_elements))
338 4488 : Thermochimica::setElementMass(_el_ids[i], _shared_real_mem[2 + i]);
339 :
340 : // Optionally ask for a re-initialization (if reinit_requested == true)
341 1908 : reinitDataMooseToTc();
342 :
343 : // Calculate thermochemical equilibrium
344 1908 : Thermochimica::thermochimica();
345 :
346 : // Check for error status
347 1908 : auto idbg = Thermochimica::checkInfoThermo();
348 1908 : 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 1908 : reinitDataMooseFromTc();
354 :
355 : // Get requested phase indices if phase concentration output was requested
356 : // i.e. if output_phases is coupled
357 1908 : auto moles_phase = Thermochimica::getMolesPhase();
358 :
359 : std::size_t idx = 0;
360 :
361 6168 : for (const auto i : make_range(_n_phases))
362 : {
363 : // Is this maybe constant? No it isn't for now
364 4260 : auto [index, idbg] = Thermochimica::getPhaseIndex(_ph_names[i]);
365 4260 : 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 4260 : if (index - 1 < 0)
369 1848 : _shared_real_mem[idx] = 0.0;
370 : else
371 2412 : _shared_real_mem[idx] = moles_phase[index - 1];
372 4260 : idx++;
373 : }
374 :
375 1908 : auto db_phases = Thermochimica::getPhaseNamesSystem();
376 1908 : auto getSpeciesMoles =
377 8160 : [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 6252 : auto [index, idbg] = Thermochimica::getPhaseIndex(phase);
384 :
385 6252 : if (Thermochimica::isPhaseMQM(
386 6252 : std::distance(db_phases.begin(), std::find(db_phases.begin(), db_phases.end(), phase))))
387 : {
388 564 : auto [fraction, idbg] = Thermochimica::getMqmqaPairMolFraction(phase, species);
389 :
390 564 : switch (_output_mass_unit)
391 : {
392 0 : case OutputMassUnit::FRACTION:
393 : {
394 : value = fraction;
395 : code = idbg;
396 0 : break;
397 : }
398 564 : case OutputMassUnit::MOLES:
399 : {
400 564 : auto [molesPair, idbgPair] = Thermochimica::getMqmqaMolesPairs(phase);
401 564 : value = molesPair * fraction;
402 564 : code = idbg + idbgPair;
403 : break;
404 : }
405 : default:
406 : break;
407 : }
408 : }
409 : else
410 : {
411 5688 : auto [fraction, idbg] = Thermochimica::getOutputMolSpeciesPhase(phase, species);
412 5688 : switch (_output_mass_unit)
413 : {
414 3552 : case OutputMassUnit::FRACTION:
415 : {
416 : value = fraction;
417 : code = idbg;
418 3552 : break;
419 : }
420 2136 : case OutputMassUnit::MOLES:
421 : {
422 2136 : value = index >= 1 ? moles_phase[index - 1] * fraction : 0.0;
423 : code = idbg;
424 : break;
425 : }
426 : default:
427 : break;
428 : }
429 : }
430 6252 : return {value, code};
431 : };
432 :
433 8160 : for (const auto i : make_range(_n_species))
434 : {
435 6252 : auto [fraction, idbg] = getSpeciesMoles(
436 6252 : _species_phase_pairs[i].first,
437 6252 : _species_phase_pairs[i].second); // can we somehow use IDs instead of strings here?
438 :
439 6252 : if (idbg == 0)
440 2868 : _shared_real_mem[idx] = fraction;
441 3384 : else if (idbg == 1)
442 3384 : _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 6252 : idx++;
453 : }
454 :
455 1908 : if (_output_element_potentials)
456 3840 : for (const auto i : index_range(_element_potentials))
457 : {
458 2664 : auto [potential, idbg] = Thermochimica::getOutputChemPot(_element_potentials[i]);
459 2664 : if (idbg == 0)
460 2640 : _shared_real_mem[idx] = potential;
461 24 : else if (idbg == 1)
462 : // element not present, just leave this at 0 for now
463 24 : _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 2664 : idx++;
472 : }
473 :
474 1908 : if (_output_vapor_pressures)
475 2544 : for (const auto i : make_range(_n_vapor_species))
476 : {
477 1368 : auto [fraction, moles, idbg] =
478 1368 : Thermochimica::getOutputMolSpecies(_vapor_phase_pairs[i].second);
479 : libmesh_ignore(moles);
480 :
481 1368 : if (idbg == 0)
482 1344 : _shared_real_mem[idx] = fraction * pressure;
483 24 : else if (idbg == 1)
484 24 : _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 1368 : idx++;
495 : }
496 :
497 1908 : if (_output_element_phases)
498 4080 : for (const auto i : make_range(_n_phase_elements))
499 : {
500 3168 : auto [moles, idbg] = Thermochimica::getElementMolesInPhase(_phase_element_pairs[i].second,
501 3168 : _phase_element_pairs[i].first);
502 :
503 3168 : if (idbg == 0)
504 3144 : _shared_real_mem[idx] = moles;
505 24 : else if (idbg == 1)
506 24 : _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 3168 : idx++;
517 : }
518 : #endif
519 : // Send message back to parent process
520 1908 : notify('B');
521 1908 : }
522 :
523 : template <bool is_nodal>
524 : void
525 1908 : ThermochimicaDataBase<is_nodal>::reinitDataMooseFromTc()
526 : {
527 : #ifdef THERMOCHIMICA_ENABLED
528 1908 : auto & d = _data[_current_id];
529 :
530 1908 : if (_reinit != ReinitializationType::NONE)
531 : {
532 1644 : Thermochimica::saveReinitData();
533 1644 : auto data = Thermochimica::getReinitData();
534 :
535 1644 : if (_reinit == ReinitializationType::TIME)
536 : {
537 384 : d._assemblage = std::move(data.assemblage);
538 384 : d._moles_phase = std::move(data.molesPhase);
539 384 : d._element_potential = std::move(data.elementPotential);
540 384 : d._chemical_potential = std::move(data.chemicalPotential);
541 384 : d._mol_fraction = std::move(data.moleFraction);
542 384 : d._elements_used = std::move(data.elementsUsed);
543 384 : d._reinit_available = data.reinitAvailable;
544 : }
545 1644 : }
546 : #endif
547 1908 : }
548 :
549 : template <bool is_nodal>
550 : void
551 1908 : ThermochimicaDataBase<is_nodal>::reinitDataMooseToTc()
552 : {
553 : #ifdef THERMOCHIMICA_ENABLED
554 : // Tell Thermochimica whether a re-initialization is requested for this calculation
555 1908 : switch (_reinit)
556 : {
557 264 : case ReinitializationType::NONE:
558 264 : Thermochimica::setReinitRequested(false);
559 : break;
560 1644 : default:
561 1644 : Thermochimica::setReinitRequested(true);
562 : }
563 : // If we have re-initialization data and want a re-initialization, then
564 : // load data into Thermochimica
565 1908 : auto it = _data.find(_current_id);
566 :
567 1908 : if (it != _data.end() &&
568 954 : _reinit == ReinitializationType::TIME) // If doing previous timestep reinit
569 : {
570 : auto & d = it->second;
571 192 : if (d._reinit_available)
572 : {
573 192 : Thermochimica::resetReinit();
574 192 : Thermochimica::ReinitializationData data;
575 192 : data.assemblage = d._assemblage;
576 192 : data.molesPhase = d._moles_phase;
577 192 : data.elementPotential = d._element_potential;
578 192 : data.chemicalPotential = d._chemical_potential;
579 192 : data.moleFraction = d._mol_fraction;
580 192 : data.elementsUsed = d._elements_used;
581 192 : Thermochimica::setReinitData(data);
582 192 : }
583 : }
584 : #endif
585 1908 : }
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>;
|