14 #include "libmesh/int_range.h" 18 #include <sys/socket.h> 21 #ifdef THERMOCHIMICA_ENABLED 22 #include "Thermochimica-cxx.h" 23 #include "checkUnits.h" 29 template <
bool is_nodal>
35 if constexpr (is_nodal)
37 params,
"Provides access to Thermochimica-calculated data at nodes.");
40 params,
"Provides access to Thermochimica-calculated data at elements.");
46 MooseEnum reinit_type(
"none time nodal",
"nodal");
48 "reinit_type", reinit_type,
"Reinitialization scheme to use with Thermochimica");
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");
55 "Elements whose molar amounts in specific phases are requested");
57 MooseEnum mUnit_op(
"mole_fraction moles",
"moles");
59 "output_species_unit", mUnit_op,
"Mass unit for output species: mole_fractions or moles");
61 if constexpr (is_nodal)
62 params.
set<
bool>(
"unique_node_execute") =
true;
65 params.
addParam<FileName>(
"thermofile",
66 "Thermodynamic file to be used for Thermochimica calculations");
76 template <
bool is_nodal>
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")),
89 "_chemical_composition_action")),
90 _el_ids(_action.elementIDs()),
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")),
102 _vp(_n_vapor_species),
103 _el_pot(_n_potentials),
104 _el_ph(_n_phase_elements),
110 mooseError(
"Element IDs size does not match number of elements.");
112 _el[i] = &coupledValue(
"elements", i);
114 if (isParamValid(
"output_phases"))
117 mooseError(
"Phase names vector size does not match number of phases.");
120 _ph[i] = &writableVariable(
"output_phases", i);
123 if (isParamValid(
"output_species"))
126 mooseError(
"Species name vector size does not match number of output species.");
129 _sp[i] = &writableVariable(
"output_species", i);
132 if (isParamValid(
"output_vapor_pressures"))
135 mooseError(
"Vapor species name vector size does not match number of output vapor species.");
138 _vp[i] = &writableVariable(
"output_vapor_pressures", i);
141 if (isParamValid(
"output_element_phases"))
144 mooseError(
"Element phase vector size does not match number of output elements in phases");
147 _el_ph[i] = &writableVariable(
"output_element_phases", i);
150 if (isParamValid(
"output_element_potentials"))
153 mooseError(
"Element potentials vector size does not match number of element potentials " 154 "specified for output.");
157 _el_pot[i] = &writableVariable(
"output_element_potentials", i);
160 const auto dofid_size = std::max( 1, 0);
161 const auto real_size =
168 static_cast<std::byte *
>(mmap(
nullptr,
170 PROT_READ | PROT_WRITE,
171 MAP_ANONYMOUS | MAP_SHARED,
174 if (shared_mem == MAP_FAILED)
175 mooseError(
"Failed to allocate shared memory for thermochimica IPC.");
183 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
184 mooseError(
"Failed to create socketpair for thermochimica IPC.");
189 mooseError(
"Fork failed for thermochimica library.");
197 #ifdef THERMOCHIMICA_ENABLED 199 if (isParamValid(
"thermofile"))
201 const auto thermo_file = this->
template getParam<FileName>(
"thermofile");
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(),
210 Thermochimica::setThermoFilename(thermo_file);
213 Thermochimica::parseThermoFile();
215 const auto idbg = Thermochimica::checkInfoThermo();
217 this->paramError(
"thermofile",
"Thermochimica data file cannot be parsed. ", idbg);
229 template <
bool is_nodal>
236 template <
bool is_nodal>
242 template <
bool is_nodal>
243 template <
typename T>
248 while (read(_socket, &msg,
sizeof(T)) == 0)
252 mooseError(
"Read error waiting for '", expect_msg,
"' ", errno,
' ', strerror(errno));
254 if (msg != expect_msg)
255 mooseError(
"Expected '", expect_msg,
"' but received '", msg,
"'");
258 template <
bool is_nodal>
259 template <
typename T>
263 if (write(_socket, &send_msg,
sizeof(T)) !=
sizeof(T))
264 mooseError(
"Failed to notify thermochimica library child process.");
267 template <
bool is_nodal>
274 const unsigned int qp = 0;
277 if constexpr (is_nodal)
278 _shared_dofid_mem[0] = this->_current_node->id();
280 _shared_dofid_mem[0] = this->_current_elem->id();
283 _shared_real_mem[0] = _temperature[qp];
284 _shared_real_mem[1] = _pressure[qp];
286 _shared_real_mem[2 + i] = (*_el[i])[qp];
298 _ph[i]->setDofValue(_shared_real_mem[
idx++], qp);
301 _sp[i]->setDofValue(_shared_real_mem[
idx++], qp);
303 if (_output_element_potentials)
304 for (
const auto i :
index_range(_element_potentials))
305 _el_pot[i]->setDofValue(_shared_real_mem[
idx++], qp);
307 if (_output_vapor_pressures)
308 for (
const auto i :
make_range(_n_vapor_species))
309 _vp[i]->setDofValue(_shared_real_mem[
idx++], qp);
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);
316 template <
bool is_nodal>
323 #ifdef THERMOCHIMICA_ENABLED 325 _current_id = _shared_dofid_mem[0];
328 auto pressure = _shared_real_mem[1];
334 Thermochimica::setElementMass(0, 0.0);
338 Thermochimica::setElementMass(_el_ids[i], _shared_real_mem[2 + i]);
341 reinitDataMooseToTc();
344 Thermochimica::thermochimica();
347 auto idbg = Thermochimica::checkInfoThermo();
353 reinitDataMooseFromTc();
357 auto moles_phase = Thermochimica::getMolesPhase();
364 auto [index, idbg] = Thermochimica::getPhaseIndex(_ph_names[i]);
366 mooseError(
"Failed to get index of phase '", _ph_names[i],
"'");
369 _shared_real_mem[
idx] = 0.0;
371 _shared_real_mem[
idx] = moles_phase[index - 1];
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>
383 auto [index, idbg] = Thermochimica::getPhaseIndex(phase);
385 if (Thermochimica::isPhaseMQM(
386 std::distance(db_phases.begin(), std::find(db_phases.begin(), db_phases.end(), phase))))
388 auto [fraction, idbg] = Thermochimica::getMqmqaPairMolFraction(phase, species);
390 switch (_output_mass_unit)
392 case OutputMassUnit::FRACTION:
398 case OutputMassUnit::MOLES:
400 auto [molesPair, idbgPair] = Thermochimica::getMqmqaMolesPairs(phase);
401 value = molesPair * fraction;
402 code = idbg + idbgPair;
411 auto [fraction, idbg] = Thermochimica::getOutputMolSpeciesPhase(phase, species);
412 switch (_output_mass_unit)
414 case OutputMassUnit::FRACTION:
420 case OutputMassUnit::MOLES:
422 value = index >= 1 ? moles_phase[index - 1] * fraction : 0.0;
430 return {
value, code};
435 auto [fraction, idbg] = getSpeciesMoles(
436 _species_phase_pairs[i].first,
437 _species_phase_pairs[i].second);
440 _shared_real_mem[
idx] = fraction;
442 _shared_real_mem[
idx] = 0.0;
445 mooseError(
"Failed to get phase speciation for phase '",
446 _species_phase_pairs[i].first,
448 _species_phase_pairs[i].second,
449 "'. Thermochimica returned ",
455 if (_output_element_potentials)
456 for (
const auto i :
index_range(_element_potentials))
458 auto [potential, idbg] = Thermochimica::getOutputChemPot(_element_potentials[i]);
460 _shared_real_mem[
idx] = potential;
463 _shared_real_mem[
idx] = 0.0;
466 mooseError(
"Failed to get element potential for element '",
467 _element_potentials[i],
468 "'. Thermochimica returned ",
474 if (_output_vapor_pressures)
475 for (
const auto i :
make_range(_n_vapor_species))
477 auto [fraction, moles, idbg] =
478 Thermochimica::getOutputMolSpecies(_vapor_phase_pairs[i].second);
484 _shared_real_mem[
idx] = 0.0;
487 mooseError(
"Failed to get vapor pressure for phase '",
488 _vapor_phase_pairs[i].first,
490 _vapor_phase_pairs[i].second,
491 "'. Thermochimica returned ",
497 if (_output_element_phases)
498 for (
const auto i :
make_range(_n_phase_elements))
500 auto [moles, idbg] = Thermochimica::getElementMolesInPhase(_phase_element_pairs[i].second,
501 _phase_element_pairs[i].first);
504 _shared_real_mem[
idx] = moles;
506 _shared_real_mem[
idx] = 0.0;
509 mooseError(
"Failed to get moles of element '",
510 _phase_element_pairs[i].second,
512 _phase_element_pairs[i].first,
513 "'. Thermochimica returned ",
523 template <
bool is_nodal>
527 #ifdef THERMOCHIMICA_ENABLED 528 auto &
d = _data[_current_id];
530 if (_reinit != ReinitializationType::NONE)
532 Thermochimica::saveReinitData();
533 auto data = Thermochimica::getReinitData();
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;
549 template <
bool is_nodal>
553 #ifdef THERMOCHIMICA_ENABLED 557 case ReinitializationType::NONE:
558 Thermochimica::setReinitRequested(
false);
561 Thermochimica::setReinitRequested(
true);
565 auto it = _data.find(_current_id);
567 if (it != _data.end() &&
570 auto &
d = it->second;
571 if (
d._reinit_available)
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);
587 template <
bool is_nodal>
591 if constexpr (!is_nodal)
592 mooseError(
"Requesting nodal data from an element object.");
593 return this->getData(node_id);
596 template <
bool is_nodal>
600 if constexpr (is_nodal)
601 mooseError(
"Requesting per element data from a nodal object.");
602 return this->getData(element_id);
605 template <
bool is_nodal>
609 const auto it = _data.find(
id);
610 if (it == _data.end())
612 if constexpr (is_nodal)
613 mooseError(
"Unable to look up data for node ",
id);
615 mooseError(
"Unable to look up data for element ",
id);
OutputMassUnit
Mass unit for output species.
static InputParameters validParams()
const std::vector< std::pair< std::string, std::string > > & _species_phase_pairs
const Data & getElementData(dof_id_type id) const
const std::size_t _n_elements
const Data & getNodalData(dof_id_type id) const
void mooseError(Args &&... args)
void reinitDataMooseFromTc()
Function to get re-initialization data from Thermochimica and save it in member variables of this Use...
typename std::conditional< is_nodal, NodalUserObject, ElementUserObject >::type ThermochimicaDataBaseParent
User object that performs a Gibbs energy minimization at each node by calling the Thermochimica code...
ThermochimicaDataBase(const InputParameters ¶meters)
std::vector< MooseWritableVariable * > _el_pot
Writable chemical potential variables for each element.
void reinitDataMooseToTc()
Function to load re-initialization data saved in this UserObject back into Thermochimica.
virtual void initialize() override
static const std::string temperature
int _socket
communication socket
const std::vector< std::string > & _ph_names
std::vector< unsigned int > _el_ids
std::vector< MooseWritableVariable * > _vp
Writable vapour pressures for each element.
const std::vector< std::pair< std::string, std::string > > & _phase_element_pairs
const std::vector< std::string > & _element_potentials
void libmesh_ignore(const Args &...)
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
const std::size_t _n_phases
InputParameters validParams()
const std::size_t _n_vapor_species
registerMooseObject("ChemicalReactionsApp", ThermochimicaNodalData)
std::vector< const VariableValue * > _el
void ThermochimicaDataBase_handler(int)
const Data & getData(dof_id_type id) const
const std::size_t _n_potentials
const std::size_t _n_species
void checkLibraryAvailability(MooseObject &self)
Check if thermochimica is available and throw an error if it is not.
dof_id_type * _shared_dofid_mem
shared memory pointer for dof_id_type values
Real * _shared_real_mem
shared memory pointer for Real values
virtual void execute() override
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< MooseWritableVariable * > _sp
Writable species amount variables.
const std::size_t _n_phase_elements
static const std::string pressure
IntRange< T > make_range(T beg, T end)
std::vector< MooseWritableVariable * > _el_ph
Writable variable for molar amounts of each element in specified phase.
void expect(T expect_msg)
The ChemicalCompositionAction sets up user objects, aux kernels, and aux variables for a thermochemis...
void addClassDescription(InputParameters ¶ms, const std::string &desc)
Add the supplied class description if thermochimica is available, otherwise add a warning message...
const std::vector< std::pair< std::string, std::string > > & _vapor_phase_pairs
auto index_range(const T &sizable)
const Elem & get(const ElemType type_in)
std::vector< MooseWritableVariable * > _ph
Writable phase amount variables.