https://mooseframework.inl.gov
Loading...
Searching...
No Matches
ThermochimicaData.C
Go to the documentation of this file.
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"
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
28
29template <bool is_nodal>
32{
34
35 if constexpr (is_nodal)
37 params, "Provides access to Thermochimica-calculated data at nodes.");
38 else
40 params, "Provides access to Thermochimica-calculated data at elements.");
41
42 params.addRequiredCoupledVar("elements", "Amounts of elements");
43 params.addRequiredCoupledVar("temperature", "Coupled temperature");
44 params.addCoupledVar("pressure", 1.0, "Pressure");
45
46 MooseEnum reinit_type("none time nodal", "nodal");
47 params.addParam<MooseEnum>(
48 "reinit_type", reinit_type, "Reinitialization scheme to use with Thermochimica");
49
50 params.addCoupledVar("output_element_potentials", "Chemical potentials of elements");
51 params.addCoupledVar("output_phases", "Amounts of phases to be output");
52 params.addCoupledVar("output_species", "Amounts of species to be output");
53 params.addCoupledVar("output_vapor_pressures", "Vapour pressures of species to be output");
54 params.addCoupledVar("output_element_phases",
55 "Elements whose molar amounts in specific phases are requested");
56
57 MooseEnum mUnit_op("mole_fraction moles", "moles");
58 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 params.set<bool>("unique_node_execute") = true;
63
64 params.addPrivateParam<ChemicalCompositionAction *>("_chemical_composition_action");
65 params.addParam<FileName>("thermofile",
66 "Thermodynamic file to be used for Thermochimica calculations");
67 return params;
68}
69
70void
72{
73 exit(0);
74}
75
76template <bool is_nodal>
78 : ThermochimicaDataBaseParent<is_nodal>(parameters),
79 _pressure(coupledValue("pressure")),
80 _temperature(coupledValue("temperature")),
81 _n_phases(coupledComponents("output_phases")),
82 _n_species(coupledComponents("output_species")),
83 _n_elements(coupledComponents("elements")),
84 _n_vapor_species(coupledComponents("output_vapor_pressures")),
85 _n_phase_elements(coupledComponents("output_element_phases")),
86 _n_potentials(coupledComponents("output_element_potentials")),
87 _el(_n_elements),
88 _action(*parameters.getCheckedPointerParam<ChemicalCompositionAction *>(
89 "_chemical_composition_action")),
90 _el_ids(_action.elementIDs()),
91 _reinit(parameters.get<MooseEnum>("reinit_type").getEnum<ReinitializationType>()),
92 _ph_names(_action.phases()),
93 _element_potentials(_action.elementPotentials()),
94 _species_phase_pairs(_action.speciesPhasePairs()),
95 _vapor_phase_pairs(_action.vaporPhasePairs()),
96 _phase_element_pairs(_action.phaseElementPairs()),
97 _output_element_potentials(isCoupled("output_element_potentials")),
98 _output_vapor_pressures(isCoupled("output_vapor_pressures")),
99 _output_element_phases(isCoupled("output_element_phases")),
100 _ph(_n_phases),
101 _sp(_n_species),
102 _vp(_n_vapor_species),
103 _el_pot(_n_potentials),
104 _el_ph(_n_phase_elements),
105 _output_mass_unit(parameters.get<MooseEnum>("output_species_unit").getEnum<OutputMassUnit>())
106{
108
109 if (_el_ids.size() != _n_elements)
110 mooseError("Element IDs size does not match number of elements.");
111 for (const auto i : make_range(_n_elements))
112 _el[i] = &coupledValue("elements", i);
113
114 if (isParamValid("output_phases"))
115 {
116 if (_ph_names.size() != _n_phases)
117 mooseError("Phase names vector size does not match number of phases.");
118
119 for (const auto i : make_range(_n_phases))
120 _ph[i] = &writableVariable("output_phases", i);
121 }
122
123 if (isParamValid("output_species"))
124 {
125 if (_species_phase_pairs.size() != _n_species)
126 mooseError("Species name vector size does not match number of output species.");
127
128 for (const auto i : make_range(_n_species))
129 _sp[i] = &writableVariable("output_species", i);
130 }
131
132 if (isParamValid("output_vapor_pressures"))
133 {
135 mooseError("Vapor species name vector size does not match number of output vapor species.");
136
137 for (const auto i : make_range(_n_vapor_species))
138 _vp[i] = &writableVariable("output_vapor_pressures", i);
139 }
140
141 if (isParamValid("output_element_phases"))
142 {
144 mooseError("Element phase vector size does not match number of output elements in phases");
145
146 for (const auto i : make_range(_n_phase_elements))
147 _el_ph[i] = &writableVariable("output_element_phases", i);
148 }
149
150 if (isParamValid("output_element_potentials"))
151 {
153 mooseError("Element potentials vector size does not match number of element potentials "
154 "specified for output.");
155
156 for (const auto i : make_range(_n_potentials))
157 _el_pot[i] = &writableVariable("output_element_potentials", i);
158 }
159 // buffer size
160 const auto dofid_size = std::max(/* send */ 1, /* receive */ 0);
161 const auto real_size =
162 std::max(/* send */ 2 + _n_elements,
163 /* receive */ _n_phases + _n_species + _element_potentials.size() +
165
166 // set up shared memory for communication with child process
167 auto shared_mem =
168 static_cast<std::byte *>(mmap(nullptr,
169 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 if (shared_mem == MAP_FAILED)
175 mooseError("Failed to allocate shared memory for thermochimica IPC.");
176
177 // set up buffer partitions
178 _shared_dofid_mem = reinterpret_cast<dof_id_type *>(shared_mem);
179 _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 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
184 mooseError("Failed to create socketpair for thermochimica IPC.");
185
186 // fork child process that will manage thermochimica calls
187 _pid = fork();
188 if (_pid < 0)
189 mooseError("Fork failed for thermochimica library.");
190 if (_pid == 0)
191 {
192 // here we are in the child process
193 _socket = sockets[0];
194 // clean exit upon SIGTERM (mainly for Civet code coverage)
195 signal(SIGTERM, ThermochimicaDataBase_handler);
196
197#ifdef THERMOCHIMICA_ENABLED
198 // Initialize database in Thermochimica
199 if (isParamValid("thermofile"))
200 {
201 const auto thermo_file = this->template getParam<FileName>("thermofile");
202
203 if (thermo_file.length() > 1024)
204 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 Thermochimica::setThermoFilename(thermo_file);
211
212 // Read in thermodynamics model, only once
213 Thermochimica::parseThermoFile();
214
215 const auto idbg = Thermochimica::checkInfoThermo();
216 if (idbg != 0)
217 this->paramError("thermofile", "Thermochimica data file cannot be parsed. ", idbg);
218 }
219#endif
220
221 while (true)
222 server();
223 }
224
225 // parent process continues here
226 _socket = sockets[1];
227}
228
229template <bool is_nodal>
231{
232 if (_pid)
233 kill(_pid, SIGTERM);
234}
235
236template <bool is_nodal>
237void
241
242template <bool is_nodal>
243template <typename T>
244void
246{
247 T msg;
248 while (read(_socket, &msg, sizeof(T)) == 0)
249 {
250 if (errno == EAGAIN)
251 continue;
252 mooseError("Read error waiting for '", expect_msg, "' ", errno, ' ', strerror(errno));
253 }
254 if (msg != expect_msg)
255 mooseError("Expected '", expect_msg, "' but received '", msg, "'");
256}
257
258template <bool is_nodal>
259template <typename T>
260void
262{
263 if (write(_socket, &send_msg, sizeof(T)) != sizeof(T))
264 mooseError("Failed to notify thermochimica library child process.");
265}
266
267template <bool is_nodal>
268void
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 _shared_dofid_mem[0] = this->_current_node->id();
279 else
280 _shared_dofid_mem[0] = this->_current_elem->id();
281
282 // store all required data in shared memory
283 _shared_real_mem[0] = _temperature[qp];
284 _shared_real_mem[1] = _pressure[qp];
285 for (const auto i : make_range(_n_elements))
286 _shared_real_mem[2 + i] = (*_el[i])[qp];
287
288 // message child process to trigger calculation
289 notify('A');
290
291 // and wait for the child process to signal end of calculation
292 expect('B');
293
294 // unpack data from shared memory
295 std::size_t idx = 0;
296
297 for (const auto i : make_range(_n_phases))
298 _ph[i]->setDofValue(_shared_real_mem[idx++], qp);
299
300 for (const auto i : make_range(_n_species))
301 _sp[i]->setDofValue(_shared_real_mem[idx++], qp);
302
303 if (_output_element_potentials)
304 for (const auto i : index_range(_element_potentials))
305 _el_pot[i]->setDofValue(_shared_real_mem[idx++], qp);
306
307 if (_output_vapor_pressures)
308 for (const auto i : make_range(_n_vapor_species))
309 _vp[i]->setDofValue(_shared_real_mem[idx++], qp);
310
311 if (_output_element_phases)
312 for (const auto i : make_range(_n_phase_elements))
313 _el_ph[i]->setDofValue(_shared_real_mem[idx++], qp);
314}
315
316template <bool is_nodal>
317void
319{
320 // wait for message from parent process
321 expect('A');
322
323#ifdef THERMOCHIMICA_ENABLED
324 // fetch data from shared memory
325 _current_id = _shared_dofid_mem[0];
326
327 auto temperature = _shared_real_mem[0];
328 auto pressure = _shared_real_mem[1];
329
330 // Set temperature and pressure for thermochemistry solver
331 Thermochimica::setTemperaturePressure(temperature, pressure);
332
333 // Reset all element masses to 0
334 Thermochimica::setElementMass(0, 0.0);
335
336 // Set element masses
337 for (const auto i : make_range(_n_elements))
338 Thermochimica::setElementMass(_el_ids[i], _shared_real_mem[2 + i]);
339
340 // Optionally ask for a re-initialization (if reinit_requested == true)
341 reinitDataMooseToTc();
342
343 // Calculate thermochemical equilibrium
344 Thermochimica::thermochimica();
345
346 // Check for error status
347 auto idbg = Thermochimica::checkInfoThermo();
348 if (idbg != 0)
349 // error out for now, but we should send a code to the parent process
350 mooseError("Thermochimica error ", idbg);
351
352 // Save data for future reinits
353 reinitDataMooseFromTc();
354
355 // Get requested phase indices if phase concentration output was requested
356 // i.e. if output_phases is coupled
357 auto moles_phase = Thermochimica::getMolesPhase();
358
359 std::size_t idx = 0;
360
361 for (const auto i : make_range(_n_phases))
362 {
363 // Is this maybe constant? No it isn't for now
364 auto [index, idbg] = Thermochimica::getPhaseIndex(_ph_names[i]);
365 if (idbg != 0)
366 mooseError("Failed to get index of phase '", _ph_names[i], "'");
367 // Convert from 1-based (fortran) to 0-based (c++) indexing
368 if (index - 1 < 0)
369 _shared_real_mem[idx] = 0.0;
370 else
371 _shared_real_mem[idx] = moles_phase[index - 1];
372 idx++;
373 }
374
375 auto db_phases = Thermochimica::getPhaseNamesSystem();
376 auto getSpeciesMoles =
377 [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 auto [index, idbg] = Thermochimica::getPhaseIndex(phase);
384
385 if (Thermochimica::isPhaseMQM(
386 std::distance(db_phases.begin(), std::find(db_phases.begin(), db_phases.end(), phase))))
387 {
388 auto [fraction, idbg] = Thermochimica::getMqmqaPairMolFraction(phase, species);
389
390 switch (_output_mass_unit)
391 {
392 case OutputMassUnit::FRACTION:
393 {
394 value = fraction;
395 code = idbg;
396 break;
397 }
398 case OutputMassUnit::MOLES:
399 {
400 auto [molesPair, idbgPair] = Thermochimica::getMqmqaMolesPairs(phase);
401 value = molesPair * fraction;
402 code = idbg + idbgPair;
403 break;
404 }
405 default:
406 break;
407 }
408 }
409 else
410 {
411 auto [fraction, idbg] = Thermochimica::getOutputMolSpeciesPhase(phase, species);
412 switch (_output_mass_unit)
413 {
414 case OutputMassUnit::FRACTION:
415 {
416 value = fraction;
417 code = idbg;
418 break;
419 }
420 case OutputMassUnit::MOLES:
421 {
422 value = index >= 1 ? moles_phase[index - 1] * fraction : 0.0;
423 code = idbg;
424 break;
425 }
426 default:
427 break;
428 }
429 }
430 return {value, code};
431 };
432
433 for (const auto i : make_range(_n_species))
434 {
435 auto [fraction, idbg] = getSpeciesMoles(
436 _species_phase_pairs[i].first,
437 _species_phase_pairs[i].second); // can we somehow use IDs instead of strings here?
438
439 if (idbg == 0)
440 _shared_real_mem[idx] = fraction;
441 else if (idbg == 1)
442 _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 idx++;
453 }
454
455 if (_output_element_potentials)
456 for (const auto i : index_range(_element_potentials))
457 {
458 auto [potential, idbg] = Thermochimica::getOutputChemPot(_element_potentials[i]);
459 if (idbg == 0)
460 _shared_real_mem[idx] = potential;
461 else if (idbg == 1)
462 // element not present, just leave this at 0 for now
463 _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 idx++;
472 }
473
474 if (_output_vapor_pressures)
475 for (const auto i : make_range(_n_vapor_species))
476 {
477 auto [fraction, moles, idbg] =
478 Thermochimica::getOutputMolSpecies(_vapor_phase_pairs[i].second);
479 libmesh_ignore(moles);
480
481 if (idbg == 0)
482 _shared_real_mem[idx] = fraction * pressure;
483 else if (idbg == 1)
484 _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 idx++;
495 }
496
497 if (_output_element_phases)
498 for (const auto i : make_range(_n_phase_elements))
499 {
500 auto [moles, idbg] = Thermochimica::getElementMolesInPhase(_phase_element_pairs[i].second,
501 _phase_element_pairs[i].first);
502
503 if (idbg == 0)
504 _shared_real_mem[idx] = moles;
505 else if (idbg == 1)
506 _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 idx++;
517 }
518#endif
519 // Send message back to parent process
520 notify('B');
521}
522
523template <bool is_nodal>
524void
526{
527#ifdef THERMOCHIMICA_ENABLED
528 auto & d = _data[_current_id];
529
530 if (_reinit != ReinitializationType::NONE)
531 {
532 Thermochimica::saveReinitData();
533 auto data = Thermochimica::getReinitData();
534
535 if (_reinit == ReinitializationType::TIME)
536 {
537 d._assemblage = std::move(data.assemblage);
538 d._moles_phase = std::move(data.molesPhase);
539 d._element_potential = std::move(data.elementPotential);
540 d._chemical_potential = std::move(data.chemicalPotential);
541 d._mol_fraction = std::move(data.moleFraction);
542 d._elements_used = std::move(data.elementsUsed);
543 d._reinit_available = data.reinitAvailable;
544 }
545 }
546#endif
547}
548
549template <bool is_nodal>
550void
552{
553#ifdef THERMOCHIMICA_ENABLED
554 // Tell Thermochimica whether a re-initialization is requested for this calculation
555 switch (_reinit)
556 {
557 case ReinitializationType::NONE:
558 Thermochimica::setReinitRequested(false);
559 break;
560 default:
561 Thermochimica::setReinitRequested(true);
562 }
563 // If we have re-initialization data and want a re-initialization, then
564 // load data into Thermochimica
565 auto it = _data.find(_current_id);
566
567 if (it != _data.end() &&
568 _reinit == ReinitializationType::TIME) // If doing previous timestep reinit
569 {
570 auto & d = it->second;
571 if (d._reinit_available)
572 {
573 Thermochimica::resetReinit();
574 Thermochimica::ReinitializationData data;
575 data.assemblage = d._assemblage;
576 data.molesPhase = d._moles_phase;
577 data.elementPotential = d._element_potential;
578 data.chemicalPotential = d._chemical_potential;
579 data.moleFraction = d._mol_fraction;
580 data.elementsUsed = d._elements_used;
581 Thermochimica::setReinitData(data);
582 }
583 }
584#endif
585}
586
587template <bool is_nodal>
590{
591 if constexpr (!is_nodal)
592 mooseError("Requesting nodal data from an element object.");
593 return this->getData(node_id);
594}
595
596template <bool is_nodal>
599{
600 if constexpr (is_nodal)
601 mooseError("Requesting per element data from a nodal object.");
602 return this->getData(element_id);
603}
604
605template <bool is_nodal>
608{
609 const auto it = _data.find(id);
610 if (it == _data.end())
611 {
612 if constexpr (is_nodal)
613 mooseError("Unable to look up data for node ", id);
614 else
615 mooseError("Unable to look up data for element ", id);
616 }
617 return it->second;
618}
619
620template class ThermochimicaDataBase<true>;
621template class ThermochimicaDataBase<false>;
const double T
void mooseError(Args &&... args)
void ThermochimicaDataBase_handler(int)
registerMooseObject("ChemicalReactionsApp", ThermochimicaNodalData)
typename std::conditional< is_nodal, NodalUserObject, ElementUserObject >::type ThermochimicaDataBaseParent
The ChemicalCompositionAction sets up user objects, aux kernels, and aux variables for a thermochemis...
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
void addPrivateParam(const std::string &name, const T &value)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
T & set(const std::string &name, bool quiet_mode=false)
void addCoupledVar(const std::string &name, const std::string &doc_string)
User object that performs a Gibbs energy minimization at each node by calling the Thermochimica code.
const std::size_t _n_phases
void reinitDataMooseToTc()
Function to load re-initialization data saved in this UserObject back into Thermochimica.
virtual void initialize() override
dof_id_type * _shared_dofid_mem
shared memory pointer for dof_id_type values
virtual void execute() override
const std::vector< std::pair< std::string, std::string > > & _vapor_phase_pairs
Real * _shared_real_mem
shared memory pointer for Real values
const std::size_t _n_elements
std::vector< MooseWritableVariable * > _vp
Writable vapour pressures for each element.
const std::vector< std::pair< std::string, std::string > > & _phase_element_pairs
OutputMassUnit
Mass unit for output species.
std::vector< MooseWritableVariable * > _el_pot
Writable chemical potential variables for each element.
const std::size_t _n_potentials
const std::size_t _n_vapor_species
std::vector< unsigned int > _el_ids
const Data & getData(dof_id_type id) const
static InputParameters validParams()
const std::size_t _n_species
const std::vector< std::string > & _ph_names
const Data & getElementData(dof_id_type id) const
void expect(T expect_msg)
std::vector< const VariableValue * > _el
const std::vector< std::pair< std::string, std::string > > & _species_phase_pairs
const std::size_t _n_phase_elements
int _socket
communication socket
std::vector< MooseWritableVariable * > _ph
Writable phase amount variables.
const std::vector< std::string > & _element_potentials
const Data & getNodalData(dof_id_type id) const
void reinitDataMooseFromTc()
Function to get re-initialization data from Thermochimica and save it in member variables of this Use...
std::vector< MooseWritableVariable * > _sp
Writable species amount variables.
ThermochimicaDataBase(const InputParameters &parameters)
std::vector< MooseWritableVariable * > _el_ph
Writable variable for molar amounts of each element in specified phase.
void addClassDescription(InputParameters &params, const std::string &desc)
Add the supplied class description if thermochimica is available, otherwise add a warning message.
void checkLibraryAvailability(MooseObject &self)
Check if thermochimica is available and throw an error if it is not.