diff --git a/src/architecture/Network.h b/src/architecture/Network.h index 66063da..f3c3cfb 100755 --- a/src/architecture/Network.h +++ b/src/architecture/Network.h @@ -303,6 +303,13 @@ class Network { */ bool isGround(int nodeId) const; + /** + * @brief Checks and returns if a node is a boundary node (connects to a CFD module). + * @param[in] nodeId Id of the node that should be checked. + * @return If the node with the specified id is a boundary node. + */ + bool isBoundary(int nodeId) const; + /** * @brief Checks and returns if an edge is a channel */ diff --git a/src/architecture/Network.hh b/src/architecture/Network.hh index 16aa0a0..fc98117 100755 --- a/src/architecture/Network.hh +++ b/src/architecture/Network.hh @@ -427,6 +427,16 @@ bool Network::isGround(int nodeId_) const { return nodes.at(nodeId_)->getGround(); } +template +bool Network::isBoundary(int nodeId_) const { + if (modularReach.count(nodeId_)) { + if (modularReach.at(nodeId_)->getModuleType() == ModuleType::LBM) { + return true; + } + } + return false; +} + template bool Network::isChannel(int edgeId_) const { return channels.count(edgeId_); diff --git a/src/simulation/MixingModels.h b/src/simulation/MixingModels.h index 7fbd0c3..4c3ff70 100644 --- a/src/simulation/MixingModels.h +++ b/src/simulation/MixingModels.h @@ -34,6 +34,9 @@ class Simulation; template class Specie; +template +class lbmSimulator; + // Structure to define the mixture inflow into a node template struct MixtureInFlow { @@ -251,13 +254,16 @@ template class DiffusionMixingModel : public MixingModel { private: - int resolution = 10; + int noFourierTerms = 100; std::set mixingNodes; std::vector>> concatenatedFlows; std::unordered_map>> outflowDistributions; std::unordered_map filledEdges; ///< Which edges are currently filled and what mixture is at the front + std::unordered_map>> concentrationFieldsOut; ///< Defines which concentration fields are defined at nodes at the interface between 1D into CFD > void generateInflows(); + std::shared_ptr> simulator; // Pointer to the lbm simulator + public: DiffusionMixingModel(); @@ -290,13 +296,38 @@ class DiffusionMixingModel : public MixingModel { void printTopology(); - std::tuple,std::vector, T> getAnalyticalSolutionConstant(T channelLength, T channelWidth, int resolution, T pecletNr, const std::vector>& parameters); + /** + * @brief Calculate the species concentration distribution across the channel width (at the end of the channel) for constant concentration flow sections. To get the analytical solution, a fourier series is employed. + */ + std::tuple,std::vector, T> getAnalyticalFunction(T channelLength, T channelWidth, int noFourierTerms, T pecletNr, const std::vector>& parameters); - std::tuple,std::vector, T> getAnalyticalSolutionFunction(T channelLength, T channelWidth, int resolution, T pecletNr, const std::vector>& parameters, std::function fConstant); + /** + * @brief Calculate the species concentration distribution across the channel width (at the end of the channel) for non-constant concentration inputs flow sections. To get the analytical solution, a fourier series is employed. + */ + std::tuple,std::vector, T> getAnalyticalFunction(T channelLength, T channelWidth, int noFourierTerms, T pecletNr, const std::vector>& parameters, std::function fConstant); - std::tuple,std::vector, T> getAnalyticalSolutionTotal(T channelLength, T currChannelFlowRate, T channelWidth, int resolution, int speciesId, T pecletNr, + /** + * @brief Calculate the species concentration distribution in a channel by calculating the get analytical function for constant and non-constant flow sections. + */ + std::tuple,std::vector, T> getAnalyticalSolution(T channelLength, T currChannelFlowRate, T channelWidth, int noFourierTerms, int specieId, T pecletNr, const std::vector>& flowSections, std::unordered_map>>& diffusiveMixtures); + /** + * @brief Calculate the species concentration across the channel width (at the end of the channel) for concentation fields flowing out of a CFD module. For this the constant concentration values at each lattice point of the CFD module are translated into linear segments. + * This way a more accurate connection, while adhering to the law of mass conservation is achieved. For the concentration distribution function at the end of a channel the analytical solution is solved by employing a fourier series. + */ + std::tuple,std::vector,T> getAnalyticalSolutionHybrid(T channelLength, T currChannelFlowRate, T channelWidth, int resolution, T pecletNr, std::vector concentrationField, T dx); + + /** + * @brief Use piecewise linerar interpolation to translate the analytical solution of the species concentration across the channel width (at the end of the channel) into the concentration values in the lattice of the CFD module. This considers the conservation of mass. + */ + std::vector defineConcentrationNodeFieldForCfdInput(int resolutionIntoCfd, int specieId, int channelId, T channelWidth, std::unordered_map>>& Mixtures, int noFourierTerms); + + /** + * @brief Use a fifth order polynomial function to translate the analytical solution of the species concentration across the channel width (at the end of the channel) into the concentration values in the lattice of the CFD module. + */ + std::tuple, std::vector, T> getAnalyticalSolutionHybridInput(T channelLength, T channelWidth, int noFourierTerms, T pecletNr, const std::vector>& parameters); + void clean(arch::Network* network); void printMixturesInNetwork(); @@ -309,6 +340,22 @@ class DiffusionMixingModel : public MixingModel { bool isDiffusive() override { return 1; }; + /** + * @brief Reset the number of Fourier terms used in the analytical solution. The higher the number the higher the precision, although this is negligible at some point. + * @param[in] newnoFourierTerms The new number of Fourier terms. + */ + void resetnoFourierTerms(int newnoFourierTerms) { + noFourierTerms = newnoFourierTerms; + } + + /** + * @brief Return the number of Fourier terms used in the analytical solution. + * @param[out] noFourierTerms The current number of Fourier terms. + */ + int getnoFourierTerms() const { + return noFourierTerms; + } + }; } // namespace sim \ No newline at end of file diff --git a/src/simulation/MixingModels.hh b/src/simulation/MixingModels.hh index 04574e9..01fbb44 100644 --- a/src/simulation/MixingModels.hh +++ b/src/simulation/MixingModels.hh @@ -4,6 +4,7 @@ #include #include #include +#include #define M_PI 3.14159265358979323846 @@ -446,8 +447,8 @@ void DiffusionMixingModel::generateInflows(T timeStep, arch::Network* netw std::unordered_map,std::vector,T>> newDistributions; for (auto& specieId : presentSpecies) { T pecletNr = (std::abs(channel->getFlowRate()) / channel->getHeight()) / (sim->getSpecie(specieId))->getDiffusivity(); - std::tuple, std::vector, T> analyticalResult = getAnalyticalSolutionTotal( - channel->getLength(), std::abs(channel->getFlowRate()), channel->getWidth(), 100, specieId, + std::tuple, std::vector, T> analyticalResult = getAnalyticalSolution( + channel->getLength(), std::abs(channel->getFlowRate()), channel->getWidth(), noFourierTerms, specieId, pecletNr, outflowDistributions.at(channel->getId()), mixtures); newDistributions.try_emplace(specieId, analyticalResult); } @@ -458,6 +459,42 @@ void DiffusionMixingModel::generateInflows(T timeStep, arch::Network* netw } } } + // Define the outflow of the CFD simulators for diffusive mixing + // These nodes are not defined as mixing nodes but they might still have a complex concentration distribution + for (auto& [key, cfdSimulator] : sim->getCFDSimulators()) { + std::unordered_map>> concentrationFieldsIn = cfdSimulator->getNodeConcentrationFields(); + + for (auto& [nodeId, opening] : cfdSimulator->getOpenings()) { + // If the node is an inflow + for (auto& [channelId, channel] : network->getChannels()) { // There is only one channel connected to a node at a CFD hybrid node connection + if (cfdSimulator->getFlowRates().at(nodeId) > 0.0 && (channel->getNodeA() == nodeId || channel->getNodeB() == nodeId)) { + std::unordered_map,std::vector,T>> newDistributions; + std::cout << "Getting here, nodeId is " + std::to_string(nodeId) << std::endl; + for (const auto& [specieId, concentrationField] : concentrationFieldsIn.at(nodeId)) { + std::cout << "Getting here 2" << std::endl; + T dx = channel->getWidth() / concentrationField.size(); + T pecletNr = (std::abs(channel->getFlowRate()) / channel->getHeight()) / (sim->getSpecie(specieId))->getDiffusivity(); + std::tuple, std::vector, T> analyticalResult = getAnalyticalSolutionHybrid( + channel->getLength(), channel->getFlowRate(), channel->getWidth(), noFourierTerms, pecletNr, concentrationField, dx); + newDistributions.try_emplace(specieId, analyticalResult); + } + //Create new DiffusiveMixture + DiffusiveMixture* newMixture = dynamic_cast*>(sim->addDiffusiveMixture(newDistributions)); + newMixture->setNonConstant(); + this->injectMixtureInEdge(newMixture->getId(), channelId); + } + else if (cfdSimulator->getFlowRates().at(nodeId) < 0.0 && (channel->getNodeA() == nodeId || channel->getNodeB() == nodeId)) { + int resolutionIntoCfd = cfdSimulator->getResolution(nodeId); + concentrationFieldsOut.try_emplace(nodeId, std::unordered_map>()); + for (const auto& [specieId, speciePtr] : sim->getSpecies()) { + std::vector concentrationFieldForCfdInput = defineConcentrationNodeFieldForCfdInput(resolutionIntoCfd, specieId, channelId, channel->getWidth(), mixtures, noFourierTerms); + concentrationFieldsOut.at(nodeId).try_emplace(specieId, concentrationFieldForCfdInput); + } + } + } + } + cfdSimulator->storeNodeConcentrationFields(concentrationFieldsOut); + } } template @@ -476,7 +513,7 @@ void DiffusionMixingModel::topologyAnalysis( arch::Network* network, int n arch::Node* node = network->getNode(nodeId).get(); concatenatedFlows.clear(); outflowDistributions.clear(); - if (!node->getGround()){ + if (!node->getGround() && !network->isBoundary(nodeId)){ // 1. List all connecting channels with angle and inflow std::vector> channelOrder; @@ -657,25 +694,48 @@ void DiffusionMixingModel::topologyAnalysis( arch::Network* network, int n outflowDistributions.at(opposed.at(0).channelId).push_back(flow4); } } else { - throw std::invalid_argument("Too many channels at node " + std::to_string(nodeId) + " for DiffusionMixingModel."); + throw std::invalid_argument("Too many channels (" + std::to_string(concatenatedFlows.size()) + ") at node " + std::to_string(nodeId) + " for DiffusionMixingModel."); } } } template void DiffusionMixingModel::propagateSpecies(arch::Network* network, Simulation* sim) { - // TODO + + // Initialization + T timeStep = 0.0; + bool inflowUpdated = true; + + // As long as the remaining timestep does not return to 0.0, we conduct the following loop + while (inflowUpdated) { + // Propagate all mixtures and check if a mixture reaches a channel end + std::cout << "[propagateSpecies][updateNodeInflow]" << std::endl; + updateNodeInflow(timeStep, network); + // Generate a new inflow in case a mixture has reached channel end. + std::cout << "[propagateSpecies][generateInflows]" << std::endl; + generateInflows(timeStep, network, sim, sim->getMixtures()); + // Clear the buffers for the diffusion mixing model + std::cout << "[propagateSpecies][clean]" << std::endl; + clean(network); + // Define and store the new minimal timestep for the next iteration + this->updateMinimalTimeStep(network); + timeStep = this->getMinimalTimeStep(); + if (timeStep < 1e-12) { + inflowUpdated = false; + } + } + } template -std::tuple, std::vector, T> DiffusionMixingModel::getAnalyticalSolutionConstant(T channelLength, T channelWidth, int resolution, T pecletNr, const std::vector>& parameters) { +std::tuple, std::vector, T> DiffusionMixingModel::getAnalyticalFunction(T channelLength, T channelWidth, int noFourierTerms, T pecletNr, const std::vector>& parameters) { T a_0 = 0.0; std::vector segmentedResult; for (const auto& parameter : parameters) { - for (int n = 1; n < resolution; n++) { + for (int n = 1; n < noFourierTerms; n++) { T a_n = (2/(n * M_PI)) * (parameter.concentrationAtChannelEnd) * (std::sin(n * M_PI * parameter.endWidth) - std::sin(n * M_PI * parameter.startWidth)); - segmentedResult.push_back(a_n * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth))); + segmentedResult.push_back(a_n * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth))); } } @@ -683,11 +743,11 @@ std::tuple, std::vector, T> DiffusionMixingModel::getA a_0 += 2 * parameter.concentrationAtChannelEnd * (parameter.endWidth - parameter.startWidth); } - auto f = [a_0, channelLength, channelWidth, resolution, pecletNr, parameters](T w) { // This returns C(w, l_1) + auto f = [a_0, channelLength, channelWidth, noFourierTerms, pecletNr, parameters](T w) { // This returns C(w, l_1) T f_sum = 0.0; for (const auto& parameter : parameters) { - for (int n = 1; n < resolution; n++) { + for (int n = 1; n < noFourierTerms; n++) { T a_n = (2/(n * M_PI)) * (parameter.concentrationAtChannelEnd) * (std::sin(n * M_PI * parameter.endWidth) - std::sin(n * M_PI * parameter.startWidth)); f_sum += a_n * std::cos(n * M_PI * (w)) * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth)); } @@ -698,7 +758,7 @@ std::tuple, std::vector, T> DiffusionMixingModel::getA } template -std::tuple,std::vector,T> DiffusionMixingModel::getAnalyticalSolutionFunction(T channelLength, T channelWidth, int resolution, T pecletNr, const std::vector>& parameters, std::function fConstant) { +std::tuple,std::vector,T> DiffusionMixingModel::getAnalyticalFunction(T channelLength, T channelWidth, int noFourierTerms, T pecletNr, const std::vector>& parameters, std::function fConstant) { // From Channel Start to Channel End for complex input std::vector segmentedResult; T a_0 = 0.0; @@ -708,13 +768,13 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna for (const auto& parameter : parameters) { T a_0_old = parameter.a_0_old; T scaleFactor = parameter.scaleFactor; - for (int n = 1; n < resolution; n++) { + for (int n = 1; n < noFourierTerms; n++) { a_n = a_0_old / (M_PI * n) * (std::sin(n * M_PI * parameter.endWidth) - std::sin(n * M_PI * parameter.startWidth)); for (long unsigned int i = 0; i < parameter.segmentedResult.size(); i++) { T translateFactor = parameter.translateFactor; - int oldN = (i % (resolution - 1)) + 1; + int oldN = (i % (noFourierTerms - 1)) + 1; if (abs(oldN/scaleFactor - n) < 1e-8) { a_n += 2 * ((0.5 * parameter.endWidth - 0.5 * parameter.startWidth) * std::cos(oldN * M_PI * translateFactor) @@ -743,14 +803,14 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna for (long unsigned int i = 0; i < parameter.segmentedResult.size(); i++) { T scaleFactor = parameter.scaleFactor; T translateFactor = parameter.translateFactor; - int oldN = (i % (resolution - 1)) + 1; + int oldN = (i % (noFourierTerms - 1)) + 1; a_0 += 2 * parameter.segmentedResult[i] * scaleFactor/(oldN * M_PI) * (std::sin(oldN * M_PI * parameter.endWidth / scaleFactor + oldN * M_PI * translateFactor) - std::sin(oldN * M_PI * parameter.startWidth / scaleFactor + oldN * M_PI * translateFactor)); } } - auto f = [a_0, channelLength, channelWidth, resolution, pecletNr, parameters, fConstant](T w) { // This returns C(w, l_1) + auto f = [a_0, channelLength, channelWidth, noFourierTerms, pecletNr, parameters, fConstant](T w) { // This returns C(w, l_1) T f_sum = 0.0; T a_n = 0.0; @@ -758,12 +818,12 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna for (const auto& parameter : parameters) { T a_0_old = parameter.a_0_old; T scaleFactor = parameter.scaleFactor; - for (int n = 1; n < resolution; n++) { + for (int n = 1; n < noFourierTerms; n++) { a_n = a_0_old / (M_PI * n) * (std::sin(n * M_PI * parameter.endWidth) - std::sin(n * M_PI * parameter.startWidth)); for (long unsigned int i = 0; i < parameter.segmentedResult.size(); i++) { T translateFactor = parameter.translateFactor; - int oldN = (i % (resolution - 1)) + 1; + int oldN = (i % (noFourierTerms - 1)) + 1; if (abs(oldN/scaleFactor - n) < 1e-12) { a_n += 2 * ((0.5 * parameter.endWidth - 0.5 * parameter.startWidth) * std::cos(oldN * M_PI * translateFactor) @@ -792,8 +852,8 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna // From Channel Start to Channel End for complex input template -std::tuple,std::vector,T> DiffusionMixingModel::getAnalyticalSolutionTotal( - T channelLength, T currChannelFlowRate, T channelWidth, int resolution, int speciesId, +std::tuple,std::vector,T> DiffusionMixingModel::getAnalyticalSolution( + T channelLength, T currChannelFlowRate, T channelWidth, int noFourierTerms, int specieId, T pecletNr, const std::vector>& flowSections, std::unordered_map>>& Mixtures) { T prevEndWidth = 0.0; @@ -819,18 +879,18 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna } else { T mixtureId = this->filledEdges.at(flowSection.channelId); // get the diffusive mixture at a specific channelId DiffusiveMixture* diffusiveMixture = dynamic_cast*>(Mixtures.at(mixtureId).get()); // Assuming diffusiveMixtures is passed - if (diffusiveMixture->getSpecieConcentrations().find(speciesId) == diffusiveMixture->getSpecieConcentrations().end()) { // the species is not in the mixture + if (diffusiveMixture->getSpecieConcentrations().find(specieId) == diffusiveMixture->getSpecieConcentrations().end()) { // the species is not in the mixture T concentration = 0.0; std::function zeroFunction = [](T) -> T { return 0.0; }; std::vector zeroSegmentedResult = {0}; constantFlowSections.push_back({startWidth, endWidth, 1.0, translateFactor, concentration, zeroFunction, zeroSegmentedResult, T(0.0)}); } else if (diffusiveMixture->getIsConstant()) { - T concentration = diffusiveMixture->getConcentrationOfSpecie(speciesId); + T concentration = diffusiveMixture->getConcentrationOfSpecie(specieId); std::function zeroFunction = [](T) -> T { return 0.0; }; std::vector zeroSegmentedResult = {0}; constantFlowSections.push_back({startWidth, endWidth, 1.0, translateFactor, concentration, zeroFunction, zeroSegmentedResult, T(0.0)}); } else { - auto specieDistribution = diffusiveMixture->getSpecieDistributions().at(speciesId); + auto specieDistribution = diffusiveMixture->getSpecieDistributions().at(specieId); // std::function concentrationFunction = specieDistribution.first; // std::vector segmentedResults = specieDistribution.second; std::function concentrationFunction = std::get<0>(specieDistribution); @@ -843,8 +903,8 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna } } - auto [fConstant, segmentedResultConstant, a_0_Constant] = getAnalyticalSolutionConstant(channelLength, channelWidth, resolution, pecletNr, constantFlowSections); - auto [fFunction, segmentedResultFunction, a_0_Function] = getAnalyticalSolutionFunction(channelLength, channelWidth, resolution, pecletNr, functionFlowSections, fConstant); + auto [fConstant, segmentedResultConstant, a_0_Constant] = getAnalyticalFunction(channelLength, channelWidth, noFourierTerms, pecletNr, constantFlowSections); + auto [fFunction, segmentedResultFunction, a_0_Function] = getAnalyticalFunction(channelLength, channelWidth, noFourierTerms, pecletNr, functionFlowSections, fConstant); segmentedResultFunction.insert(segmentedResultFunction.end(), segmentedResultConstant.begin(), segmentedResultConstant.end()); @@ -854,7 +914,165 @@ std::tuple,std::vector,T> DiffusionMixingModel::getAna } +template +std::tuple,std::vector,T> DiffusionMixingModel::getAnalyticalSolutionHybrid( + T channelLength, T currChannelFlowRate, T channelWidth, int noFourierTerms, T pecletNr, std::vector concentrationField, T dx) { + + T distance2Wall = dx / 2.0; + std::vector segmentedResult; + std::vector a_n_values(noFourierTerms, 0.0); + T a_0 = 0.0; + + for (size_t i = 0; i < concentrationField.size() - 1; ++i) { + T width1 = distance2Wall + i * dx; + T concentration1 = concentrationField[i]; + T width2 = distance2Wall + (i + 1) * dx; + T concentration2 = concentrationField[i + 1]; + + T linearSegmentFactor = (concentration2 - ((concentration1 * width2) / width1)) / (1 - (width2 / width1)); + T linearSegmentSlope = (concentration1 - linearSegmentFactor) / width1; + + a_0 += 2 * linearSegmentFactor * (width2 - width1) + linearSegmentSlope * (pow(width2, 2) - pow(width1, 2)); + + for (int n = 1; n < noFourierTerms; n++) { + T a_n = 2 / (n * M_PI) * std::sin(n * M_PI * width2) * (linearSegmentFactor + linearSegmentSlope * width2) + 2 / pow((n * M_PI), 2) * linearSegmentSlope * std::cos(n * M_PI * width2) + - 2 / (n * M_PI) * std::sin(n * M_PI * width1) * (linearSegmentFactor + linearSegmentSlope * width1) - 2 / pow((n * M_PI), 2) * linearSegmentSlope * std::cos(n * M_PI * width1); + segmentedResult.push_back(a_n * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth))); + a_n_values[n] += a_n; + } + } + // add the connection to the sides + // start segment + T concentrationStart = concentrationField[0]; + T startWidth = 0.0; + a_0 += 2 * (concentrationStart * (distance2Wall - startWidth)); + for (int n = 1; n < noFourierTerms; n++) { + T a_n_start = (2/(n * M_PI)) * concentrationStart * (std::sin(n * M_PI * distance2Wall) - std::sin(n * M_PI * startWidth)); + a_n_values[n] += a_n_start; + segmentedResult.insert(segmentedResult.begin(), a_n_start * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth))); + } + // end segment + T concentrationEnd = concentrationField[concentrationField.size() - 1]; + T endWidth = dx * concentrationField.size(); + a_0 += 2 * (concentrationEnd * (endWidth - (endWidth - distance2Wall))); + for (int n = 1; n < noFourierTerms; n++) { + T a_n_end = (2/(n * M_PI)) * concentrationEnd * (std::sin(n * M_PI * endWidth) - std::sin(n * M_PI * (endWidth - distance2Wall))); + a_n_values[n] += a_n_end; + segmentedResult.push_back(a_n_end * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth))); + } + + auto functionHybrid = [a_0, a_n_values, channelLength, channelWidth, noFourierTerms, pecletNr](T w) { // This returns C(w, l_1) + T f_sum = 0.0; + for (int n = 1; n < noFourierTerms; n++) { + f_sum += a_n_values[n] * std::cos(n * M_PI * w) * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength / channelWidth)); + } + return 0.5 * a_0 + f_sum; + }; + return {functionHybrid, segmentedResult, a_0}; +} + +template +std::vector DiffusionMixingModel::defineConcentrationNodeFieldForCfdInput(int resolutionIntoCfd, int specieId, int channelId, T channelWidth, std::unordered_map>>& Mixtures, int noFourierTerms) { + // To adhere to the conservation of mass, the sum of concentration passed must be equal to that represented by the concentration function across the width. + // For this piecewise linerar interpolation is employed. + std::vector concentrationField; + int mixtureId = 0; + if (this->filledEdges.count(channelId)) { + mixtureId = this->filledEdges.at(channelId); // get the diffusive mixture at a specific channelId + } else { + concentrationField = std::vector (resolutionIntoCfd, 0.0); + return concentrationField; + } + + DiffusiveMixture* diffusiveMixture = dynamic_cast*>(Mixtures.at(mixtureId).get()); // Assuming diffusiveMixtures is passed + if (diffusiveMixture->getSpecieConcentrations().find(specieId) == diffusiveMixture->getSpecieConcentrations().end()) { // the species is not in the mixture + // here we could distinguish between constant and non-constant concentration + concentrationField = std::vector(resolutionIntoCfd, 0.0); + } else if (diffusiveMixture->getIsConstant()) { + T concentration = diffusiveMixture->getConcentrationOfSpecie(specieId); + concentrationField = std::vector(resolutionIntoCfd, concentration); + } else { + auto specieDistribution = diffusiveMixture->getSpecieDistributions().at(specieId); + std::vector segmentedResult = std::get<1>(specieDistribution); + T a_0_old = std::get<2>(specieDistribution); + for (int i = 0; i < resolutionIntoCfd - 1; i++) { + T segmentWidth = channelWidth / resolutionIntoCfd; + T start = i * segmentWidth; + T end = (i + 1) * segmentWidth; + // now we to integrate the function over the width of each dx + T concentration = 0.5 * M_PI * a_0_old * (end - start); + for (long unsigned int n = 0; n < segmentedResult.size(); n++) { + int oldN = (n % (noFourierTerms - 1)) + 1; + concentration += segmentedResult[n] / oldN * (std::sin(oldN * M_PI * end) - std::sin(oldN * M_PI * start)); + } + concentrationField.push_back(concentration); + } + } + return concentrationField; +} + +template +std::tuple, std::vector, T> DiffusionMixingModel::getAnalyticalSolutionHybridInput( + T channelLength, T channelWidth, int noFourierTerms, T pecletNr, const std::vector>& parameters) { + T a_0 = 0.0; + std::vector segmentedResult; + + // TODO - get the parameters a to f from the polynomial fit of the hybrid concentration output + for (const auto& parameter : parameters) { + for (int n = 1; n < noFourierTerms; n++) { + T a_n = 1 / pow(n * M_PI, 6) * (n * M_PI * std::sin(n * M_PI * parameter.endWidth) * (parameter.a * pow(n * M_PI, 4) * pow(parameter.endWidth, 5) - 20 * parameter.a * pow(n * M_PI, 2) * pow(parameter.endWidth, 3) + + 120 * parameter.a * parameter.endWidth + parameter.b * (pow(n * M_PI, 4) * pow(parameter.endWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.endWidth, 2) + 24) + + parameter.c * pow(n * M_PI, 4) * pow(parameter.endWidth, 3) - 6 * parameter.c * pow(n * M_PI, 2) * parameter.endWidth + parameter.d * pow(n * M_PI, 4) * pow(parameter.endWidth, 2) + - 2 * parameter.d * pow(n * M_PI, 2) + parameter.e * pow(n * M_PI, 4) * parameter.endWidth + parameter.f * pow(n * M_PI, 4)) + + std::cos(n * M_PI * parameter.endWidth) * (5 * parameter.a * (pow(n * M_PI, 4) * pow(parameter.endWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.endWidth, 2) + 24) + + pow(n * M_PI, 2) * (4 * parameter.b * pow(n * M_PI, 2) * pow(parameter.endWidth, 3) - 24 * parameter.b * parameter.endWidth + 3 * parameter.c * pow(n * M_PI, 2) * pow(parameter.endWidth, 2) - 6 * parameter.c + + 2 * parameter.d * pow(n * M_PI, 2) * parameter.endWidth + parameter.e * pow(n * M_PI, 2)))) + - + (1 / pow(n * M_PI, 6) * (n * M_PI * std::sin(n * M_PI * parameter.startWidth) * (parameter.a * pow(n * M_PI, 4) * pow(parameter.startWidth, 5) - 20 * parameter.a * pow(n * M_PI, 2) * pow(parameter.startWidth, 3) + + 120 * parameter.a * parameter.startWidth + parameter.b * (pow(n * M_PI, 4) * pow(parameter.startWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.startWidth, 2) + 24) + + parameter.c * pow(n * M_PI, 4) * pow(parameter.startWidth, 3) - 6 * parameter.c * pow(n * M_PI, 2) * parameter.startWidth + parameter.d * pow(n * M_PI, 4) * pow(parameter.startWidth, 2) + - 2 * parameter.d * pow(n * M_PI, 2) + parameter.e * pow(n * M_PI, 4) * parameter.startWidth + parameter.f * pow(n * M_PI, 4)) + + std::cos(n * M_PI * parameter.startWidth) * (5 * parameter.a * (pow(n * M_PI, 4) * pow(parameter.startWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.startWidth, 2) + 24) + + pow(n * M_PI, 2) * (4 * parameter.b * pow(n * M_PI, 2) * pow(parameter.startWidth, 3) - 24 * parameter.b * parameter.endWidth + 3 * parameter.c * pow(n * M_PI, 2) * pow(parameter.startWidth, 2) - 6 * parameter.c + + 2 * parameter.d * pow(n * M_PI, 2) * parameter.endWidth + parameter.e * pow(n * M_PI, 2))))); + segmentedResult.push_back(a_n * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth))); + } + } + + for (const auto& parameter : parameters) { // iterates through all channels that flow into the current channel + a_0 += parameter.a / 6 * pow(parameter.endWidth, 6) + parameter.b / 5 * pow(parameter.endWidth, 5) + parameter.c / 4 * pow(parameter.endWidth, 4) + parameter.d / 3 * pow(parameter.endWidth, 3) + parameter.e / 2 * pow(parameter.endWidth, 2) + parameter.f * parameter.endWidth + - (parameter.a / 6 * pow(parameter.startWidth, 6) + parameter.b / 5 * pow(parameter.startWidth, 5) + parameter.c / 4 * pow(parameter.startWidth, 4) + parameter.d / 3 * pow(parameter.startWidth, 3) + parameter.e / 2 * pow(parameter.startWidth, 2) + parameter.f * parameter.startWidth); + } + + auto f = [a_0, channelLength, channelWidth, noFourierTerms, pecletNr, parameters](T w) { // This returns C(w, l_1) + T f_sum = 0.0; + + for (const auto& parameter : parameters) { + for (int n = 1; n < noFourierTerms; n++) { + T a_n = 1 / pow(n * M_PI, 6) * (n * M_PI * std::sin(n * M_PI * parameter.endWidth) * (parameter.a * pow(n * M_PI, 4) * pow(parameter.endWidth, 5) - 20 * parameter.a * pow(n * M_PI, 2) * pow(parameter.endWidth, 3) + + 120 * parameter.a * parameter.endWidth + parameter.b * (pow(n * M_PI, 4) * pow(parameter.endWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.endWidth, 2) + 24) + + parameter.c * pow(n * M_PI, 4) * pow(parameter.endWidth, 3) - 6 * parameter.c * pow(n * M_PI, 2) * parameter.endWidth + parameter.d * pow(n * M_PI, 4) * pow(parameter.endWidth, 2) + - 2 * parameter.d * pow(n * M_PI, 2) + parameter.e * pow(n * M_PI, 4) * parameter.endWidth + parameter.f * pow(n * M_PI, 4)) + + std::cos(n * M_PI * parameter.endWidth) * (5 * parameter.a * (pow(n * M_PI, 4) * pow(parameter.endWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.endWidth, 2) + 24) + + pow(n * M_PI, 2) * (4 * parameter.b * pow(n * M_PI, 2) * pow(parameter.endWidth, 3) - 24 * parameter.b * parameter.endWidth + 3 * parameter.c * pow(n * M_PI, 2) * pow(parameter.endWidth, 2) - 6 * parameter.c + + 2 * parameter.d * pow(n * M_PI, 2) * parameter.endWidth + parameter.e * pow(n * M_PI, 2)))) + - + (1 / pow(n * M_PI, 6) * (n * M_PI * std::sin(n * M_PI * parameter.startWidth) * (parameter.a * pow(n * M_PI, 4) * pow(parameter.startWidth, 5) - 20 * parameter.a * pow(n * M_PI, 2) * pow(parameter.startWidth, 3) + + 120 * parameter.a * parameter.startWidth + parameter.b * (pow(n * M_PI, 4) * pow(parameter.startWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.startWidth, 2) + 24) + + parameter.c * pow(n * M_PI, 4) * pow(parameter.startWidth, 3) - 6 * parameter.c * pow(n * M_PI, 2) * parameter.startWidth + parameter.d * pow(n * M_PI, 4) * pow(parameter.startWidth, 2) + - 2 * parameter.d * pow(n * M_PI, 2) + parameter.e * pow(n * M_PI, 4) * parameter.startWidth + parameter.f * pow(n * M_PI, 4)) + + std::cos(n * M_PI * parameter.startWidth) * (5 * parameter.a * (pow(n * M_PI, 4) * pow(parameter.startWidth, 4) - 12 * pow(n * M_PI, 2) * pow(parameter.startWidth, 2) + 24) + + pow(n * M_PI, 2) * (4 * parameter.b * pow(n * M_PI, 2) * pow(parameter.startWidth, 3) - 24 * parameter.b * parameter.startWidth + 3 * parameter.c * pow(n * M_PI, 2) * pow(parameter.startWidth, 2) - 6 * parameter.c + + 2 * parameter.d * pow(n * M_PI, 2) * parameter.startWidth + parameter.e * pow(n * M_PI, 2))))); + + f_sum += a_n * std::cos(n * M_PI * (w)) * std::exp(-pow(n, 2) * pow(M_PI, 2) * (1 / pecletNr) * (channelLength/channelWidth)); + } + } + return 0.5 * a_0 + f_sum; + }; + return {f, segmentedResult, a_0}; +} template void DiffusionMixingModel::printTopology() { @@ -890,6 +1108,7 @@ void DiffusionMixingModel::clean(arch::Network* network) { } } } + concentrationFieldsOut.clear(); } template diff --git a/src/simulation/simulators/cfdSimulator.h b/src/simulation/simulators/cfdSimulator.h index fadb0ce..47ca506 100755 --- a/src/simulation/simulators/cfdSimulator.h +++ b/src/simulation/simulators/cfdSimulator.h @@ -203,6 +203,17 @@ class CFDSimulator { */ virtual std::unordered_map getFlowRates() const = 0; + /** + * @brief Get the lattice resolution at the boundary nodes. + * @param[in] nodeId of the boundary node. + * @returns Resolution. + */ + virtual int getResolution(int nodeId) const + { + throw std::runtime_error("The function getResolution is undefined for this CFD simulator."); + return 0; + } + /** * @brief Store the abstract concentrations at the nodes on the module boundary in the simulator. * @param[in] concentrations Map of concentrations and node ids. @@ -212,16 +223,35 @@ class CFDSimulator { throw std::runtime_error("The function storeConcentrations is undefined for this CFD simulator."); } + /** + * @brief Store the abstract concentration fields at the nodes on the module boundary in the simulator. + * @param[in] concentrationFieldsOut Map consisting of species id and a vector of concentrations at each grid point and node ids. + */ + virtual void storeNodeConcentrationFields(std::unordered_map>> concentrationFieldsOut) + { + throw std::runtime_error("The function storeNodeConcentrationFields is undefined for this CFD simulator."); + } + /** * @brief Get the concentrations at the boundary nodes. * @returns Concentrations */ virtual std::unordered_map> getConcentrations() const { - throw std::runtime_error("The function storeConcentrations is undefined for this CFD simulator."); + throw std::runtime_error("The function getConcentrations is undefined for this CFD simulator."); return std::unordered_map>(); } + /** + * @brief Get the concentrations at the boundary nodes. + * @returns Concentrations + */ + virtual std::unordered_map>> getNodeConcentrationFields() const + { + throw std::runtime_error("The function getNodeConcentrationFields is undefined for this CFD simulator."); + return std::unordered_map>>(); + } + /** * @brief Set the boundary values on the lattice at the module nodes. * @param[in] iT Iteration step. diff --git a/src/simulation/simulators/olbContinuous.h b/src/simulation/simulators/olbContinuous.h index 03d355e..9ff6572 100644 --- a/src/simulation/simulators/olbContinuous.h +++ b/src/simulation/simulators/olbContinuous.h @@ -74,6 +74,7 @@ using BounceBack = olb::BounceBack; std::shared_ptr> converter; ///< Object that stores conversion factors from phyical to lattice parameters. std::unordered_map>> fluxes; ///< Map of fluxes at module nodes. std::unordered_map>> meanPressures; ///< Map of mean pressure values at module nodes. + std::unordered_map resolutions; ///< Vector of Resolutions at module nodes auto& getConverter() { return *converter; @@ -262,6 +263,15 @@ using BounceBack = olb::BounceBack; return resolution; }; + /** + * @brief Get the resolution at a boundary node. + * @param[in] nodeId id of the node. + * @returns resolution + */ + int getResolution(int nodeId) const override { + return resolutions.at(nodeId); + } + /** * @brief Get the convergence criterion. * @returns epsilon. diff --git a/src/simulation/simulators/olbContinuous.hh b/src/simulation/simulators/olbContinuous.hh index 46b0370..5d49fcb 100644 --- a/src/simulation/simulators/olbContinuous.hh +++ b/src/simulation/simulators/olbContinuous.hh @@ -12,6 +12,9 @@ lbmSimulator::lbmSimulator ( epsilon(epsilon_), relaxationTime(relaxationTime_) { this->cfdModule->setModuleTypeLbm(); + for (auto& [key, opening] : openings_) { + resolutions.try_emplace(key, resolution_); + } } template diff --git a/src/simulation/simulators/olbMixing.h b/src/simulation/simulators/olbMixing.h index 368c4e1..82d9400 100644 --- a/src/simulation/simulators/olbMixing.h +++ b/src/simulation/simulators/olbMixing.h @@ -45,6 +45,7 @@ using NoADDynamics = olb::NoDynamics; protected: std::unordered_map> concentrations; ///< Vector of concentration values at module nodes. > + std::unordered_map>> nodeConcentrationFields; ///< Vector of concentration values at module nodes. >> std::unordered_map*> species; @@ -187,12 +188,24 @@ using NoADDynamics = olb::NoDynamics; */ void storeConcentrations(std::unordered_map> concentrations) override; + /** + * @brief Store the abstract concentration fields at the nodes on the module boundary in the simulator. + * @param[in] concentrations Map of concentration vectors and node ids for each speciesId. + */ + void storeNodeConcentrationFields(std::unordered_map>> nodeConcentrationFields) override; + /** * @brief Get the concentrations at the boundary nodes. * @returns Concentrations */ std::unordered_map> getConcentrations() const override; + /** + * @brief Get the concentration field at the boundary nodes, i.e., the concentration at each grid end point in the lattice. + * @returns Concentration vectors + */ + std::unordered_map>> getNodeConcentrationFields() const override; + /** * @brief Returns whether the module has converged or not. * @returns Boolean for module convergence. diff --git a/src/simulation/simulators/olbMixing.hh b/src/simulation/simulators/olbMixing.hh index 188ddcb..ccecf00 100644 --- a/src/simulation/simulators/olbMixing.hh +++ b/src/simulation/simulators/olbMixing.hh @@ -246,10 +246,13 @@ void lbmMixingSimulator::initValueContainers () { this->pressures.try_emplace(key, (T) 0.0); this->flowRates.try_emplace(key, (T) 0.0); std::unordered_map tmpConcentrations; + std::unordered_map> tmpFieldConcentrations; for (auto& [speciesId, speciesPtr] : species) { tmpConcentrations.try_emplace(speciesId, 0.0); + tmpFieldConcentrations.try_emplace(speciesId, std::vector(this->getResolution(key), 0.0)); } this->concentrations.try_emplace(key, tmpConcentrations); + this->nodeConcentrationFields.try_emplace(key, tmpFieldConcentrations); } } @@ -395,11 +398,21 @@ void lbmMixingSimulator::storeConcentrations(std::unordered_mapconcentrations = concentrations_; } +template +void lbmMixingSimulator::storeNodeConcentrationFields(std::unordered_map>> concentrationFieldsOut_) { + this->nodeConcentrationFields = concentrationFieldsOut_; +} + template std::unordered_map> lbmMixingSimulator::getConcentrations() const { return this->concentrations; } +template +std::unordered_map>> lbmMixingSimulator::getNodeConcentrationFields() const { + return this->nodeConcentrationFields; +} + template bool lbmMixingSimulator::hasAdConverged() const { bool c = true;