diff --git a/CHANGELOG.md b/CHANGELOG.md index 871e8688ad..d49d2418d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the Lethe project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). +## [Master] - 2024-12-03 + +### Removed + +- MAJOR The ability to bound the void fraction from below and above (using l2 lower bound and l2 upper bound parameters) has been removed. This bounding of the void fraction was highly problematic, since it could create discontinuities in the time derivative of the void fraction and contaminate the solution. In reality, this feature was never used. Importantly, this PR refactors the calculation of the void fraction outside of the VANS and CFD-DEM solvers of Lethe so that it exists only independently in a seperate subequation solver. This change is in preperation for the matrix free implementation of the VANS equations. [#1392](https://github.com/chaos-polymtl/lethe/pull/1392) + ## Release of Lethe v1.0 - 2024-11-30 The lethe v1.0 release marks the transition of lethe to a numbered released format. Following numbered releases will be generated more often and we aim to release a numbered version 4 to 6 times per year or when sufficient changes have been realized. diff --git a/applications_tests/lethe-fluid-particles/adaptive_sparse_contacts.prm b/applications_tests/lethe-fluid-particles/adaptive_sparse_contacts.prm index 1cf7a1f9d5..ba76a4da40 100644 --- a/applications_tests/lethe-fluid-particles/adaptive_sparse_contacts.prm +++ b/applications_tests/lethe-fluid-particles/adaptive_sparse_contacts.prm @@ -164,7 +164,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.0001 - set l2 lower bound = 0 end # -------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/conserve_phase_volumes.prm b/applications_tests/lethe-fluid-particles/conserve_phase_volumes.prm index 69798de6e2..3d19a1efd6 100644 --- a/applications_tests/lethe-fluid-particles/conserve_phase_volumes.prm +++ b/applications_tests/lethe-fluid-particles/conserve_phase_volumes.prm @@ -74,9 +74,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.00000125 - set l2 lower bound = 0 - set l2 upper bound = 1 - set bound void fraction = false end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/dynamic_contact_search.prm b/applications_tests/lethe-fluid-particles/dynamic_contact_search.prm index 061faabd7a..0a01cd19ae 100644 --- a/applications_tests/lethe-fluid-particles/dynamic_contact_search.prm +++ b/applications_tests/lethe-fluid-particles/dynamic_contact_search.prm @@ -73,9 +73,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.00001 - set bound void fraction = false - set l2 lower bound = 0 - set l2 upper bound = 1 end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/liquid_fluidized_bed.prm b/applications_tests/lethe-fluid-particles/liquid_fluidized_bed.prm index e967224176..0ef4eaf63d 100644 --- a/applications_tests/lethe-fluid-particles/liquid_fluidized_bed.prm +++ b/applications_tests/lethe-fluid-particles/liquid_fluidized_bed.prm @@ -75,9 +75,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.00000125 - set l2 lower bound = 0 - set l2 upper bound = 1 - set bound void fraction = false end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/particle_sedimentation.prm b/applications_tests/lethe-fluid-particles/particle_sedimentation.prm index 12d53c92b7..e326dc65f7 100644 --- a/applications_tests/lethe-fluid-particles/particle_sedimentation.prm +++ b/applications_tests/lethe-fluid-particles/particle_sedimentation.prm @@ -73,9 +73,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.00001 - set bound void fraction = false - set l2 lower bound = 0 - set l2 upper bound = 1 end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/periodic_particles_qcm.prm b/applications_tests/lethe-fluid-particles/periodic_particles_qcm.prm index 6b02f0f7b8..72216605c4 100644 --- a/applications_tests/lethe-fluid-particles/periodic_particles_qcm.prm +++ b/applications_tests/lethe-fluid-particles/periodic_particles_qcm.prm @@ -146,7 +146,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.0005 - set bound void fraction = false end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/periodic_particles_qcm_opposite_flow.prm b/applications_tests/lethe-fluid-particles/periodic_particles_qcm_opposite_flow.prm index e039edba81..093fe4e402 100644 --- a/applications_tests/lethe-fluid-particles/periodic_particles_qcm_opposite_flow.prm +++ b/applications_tests/lethe-fluid-particles/periodic_particles_qcm_opposite_flow.prm @@ -146,7 +146,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.0005 - set bound void fraction = false end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/restart-gas-solid-fluidized-bed.prm b/applications_tests/lethe-fluid-particles/restart-gas-solid-fluidized-bed.prm index 30b1a4bd58..0b47891d4e 100644 --- a/applications_tests/lethe-fluid-particles/restart-gas-solid-fluidized-bed.prm +++ b/applications_tests/lethe-fluid-particles/restart-gas-solid-fluidized-bed.prm @@ -78,9 +78,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.000005 - set l2 lower bound = 0 - set l2 upper bound = 1 - set bound void fraction = false end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/restart_particle_sedimentation.prm b/applications_tests/lethe-fluid-particles/restart_particle_sedimentation.prm index 492fc004d2..25225f946e 100644 --- a/applications_tests/lethe-fluid-particles/restart_particle_sedimentation.prm +++ b/applications_tests/lethe-fluid-particles/restart_particle_sedimentation.prm @@ -73,9 +73,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.00001 - set bound void fraction = false - set l2 lower bound = 0 - set l2 upper bound = 1 end #--------------------------------------------------- diff --git a/applications_tests/lethe-fluid-particles/spouted_bed_load_balancing.prm b/applications_tests/lethe-fluid-particles/spouted_bed_load_balancing.prm index 7503f38cc6..5e7c4e8e9a 100644 --- a/applications_tests/lethe-fluid-particles/spouted_bed_load_balancing.prm +++ b/applications_tests/lethe-fluid-particles/spouted_bed_load_balancing.prm @@ -59,7 +59,6 @@ subsection void fraction set qcm sphere equal cell volume = true set read dem = true set dem file name = dem - set bound void fraction = false end #--------------------------------------------------- diff --git a/doc/source/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.rst b/doc/source/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.rst index bd0464f8bc..10d8ffbb0a 100644 --- a/doc/source/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.rst +++ b/doc/source/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.rst @@ -274,7 +274,7 @@ Void Fraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since we are calculating the void fraction using the particle insertion of the DEM simulation, we set the ``mode`` to ``dem``. For this, we need to read the dem files which we already wrote using check-pointing. We, therefore, set the ``read dem`` to ``true`` and specify the prefix of the dem files to be dem. We choose to use the quadrature centered method (`QCM <../../../theory/unresolved_cfd-dem/unresolved_cfd-dem.html>`_) to calculate the void fraction. For this, we specify the ``mode`` to be ``qcm``. We want the radius of our volume averaging sphere to be equal to the length of the element where the void fraction is being calculated. We don't want the volume of the sphere to be equal to the volume of the element. -For this, we set the ``qcm sphere equal cell volume`` equals to ``false``. Since we want to keep the mass conservative properties of the :math:`L^2` projection, we do not bound the void fraction and as such we set ``bound void fraction`` to ``false``. Unlike the other schemes, we do not smooth the void fraction as we usually do using the PCM and SPM void fraction schemes since QCM is continuous in time and space. +For this, we set the ``qcm sphere equal cell volume`` equals to ``false``. Unlike the other schemes, we do not smooth the void fraction as we usually do using the PCM and SPM void fraction schemes since QCM is continuous in time and space. .. code-block:: text @@ -283,7 +283,6 @@ For this, we set the ``qcm sphere equal cell volume`` equals to ``false``. Since set qcm sphere equal cell volume = false set read dem = true set dem file name = dem - set bound void fraction = false end CFD-DEM diff --git a/doc/source/examples/unresolved-cfd-dem/cylindrical-packed-bed/cylindrical-packed-bed.rst b/doc/source/examples/unresolved-cfd-dem/cylindrical-packed-bed/cylindrical-packed-bed.rst index a5e4f3b2df..d6a68a18d0 100644 --- a/doc/source/examples/unresolved-cfd-dem/cylindrical-packed-bed/cylindrical-packed-bed.rst +++ b/doc/source/examples/unresolved-cfd-dem/cylindrical-packed-bed/cylindrical-packed-bed.rst @@ -296,7 +296,7 @@ The additional sections that define the VANS solver are the void fraction subsec Void Fraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Since we are calculating the void fraction using the packed bed of the DEM simulation, we set the mode to ``dem``. For this, we need to read the dem files which we already wrote using check-pointing. We therefore set the read dem to ``true`` and specify the prefix of the ``dem`` files to be read. In order to ensure that our void fraction projection is bounded, we choose an upper bound limit of 1. We decide not to lower bound the void fraction and thus attributed a value of 0 to the L2 lower bound parameter. We now choose a smoothing factor for the void fraction as to reduce discontinuity which can lead to oscillations in the velocity. The factor we choose is around the square of twice the particle's diameter. +Since we are calculating the void fraction using the packed bed of the DEM simulation, we set the mode to ``dem``. For this, we need to read the dem files which we already wrote using check-pointing. We therefore set the read dem to ``true`` and specify the prefix of the ``dem`` files to be read. We now choose a smoothing factor for the void fraction as to reduce discontinuity which can lead to oscillations in the velocity. The factor we choose is around the square of twice the particle's diameter. .. code-block:: text diff --git a/doc/source/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.rst b/doc/source/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.rst index 52586b8231..ac9f070b41 100644 --- a/doc/source/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.rst +++ b/doc/source/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.rst @@ -308,7 +308,7 @@ The additional sections for the CFD-DEM simulations are the void fraction subsec Void Fraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Since we are calculating the void fraction using the packed bed of the DEM simulation, we set the mode to "dem". For this, we need to read the dem files which we already wrote using check-pointing. We, therefore, set the read dem to "true" and specify the prefix of the dem files to be dem. In order to ensure that our void fraction projection is bounded, we choose an upper bound limit of 1. We decide not to lower bound the void fraction and thus attributed a value of 0 to the L2 lower bound parameter. We now choose a smoothing factor for the void fraction to reduce discontinuity which can lead to oscillations in the velocity. The factor we choose is around the square of twice the particle's diameter. +Since we are calculating the void fraction using the packed bed of the DEM simulation, we set the mode to "dem". For this, we need to read the dem files which we already wrote using check-pointing. We, therefore, set the read dem to "true" and specify the prefix of the dem files to be dem. We now choose a smoothing factor for the void fraction to reduce discontinuity which can lead to oscillations in the velocity. The factor we choose is around the square of twice the particle's diameter. .. code-block:: text @@ -317,9 +317,6 @@ Since we are calculating the void fraction using the packed bed of the DEM simul set read dem = true set dem file name = dem set l2 smoothing factor = 0.000005 - set l2 lower bound = 0 - set l2 upper bound = 1 - set bound void fraction = false end CFD-DEM diff --git a/doc/source/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.rst b/doc/source/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.rst index a80ef0d36d..363fc33000 100644 --- a/doc/source/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.rst +++ b/doc/source/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.rst @@ -307,7 +307,7 @@ Void Fraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since we are calculating the void fraction using the packed bed of the DEM simulation, we set the ``mode`` to ``dem``. For this, we need to read the dem files which we already wrote using check-pointing. We, therefore, set the ``read dem`` to ``true`` and specify the prefix of the dem files to be dem. We choose to use the quadrature centered method (QCM) to calculate the void fraction. This method does not require smoothing the void fraction as it is space and time continuous. For this simulation, we use a reference sphere having the same volume as the mesh elements as the averaging volume to calculate the void fraction. -For this, we specify the ``mode`` to be ``qcm``. We want the volume of the volume averaging sphere to be equal to the volume of the element. For this, we set the ``qcm sphere equal cell volume`` equals to ``true``. Since we want to keep the mass conservative properties of the :math:`L^2` projection, we do not bound the void fraction and as such we set ``bound void fraction`` to ``false``. +For this, we specify the ``mode`` to be ``qcm``. We want the volume of the volume averaging sphere to be equal to the volume of the element. For this, we set the ``qcm sphere equal cell volume`` equals to ``true``. .. code-block:: text @@ -316,7 +316,6 @@ For this, we specify the ``mode`` to be ``qcm``. We want the volume of the volum set qcm sphere equal cell volume = true set read dem = true set dem file name = dem - set bound void fraction = false end CFD-DEM diff --git a/doc/source/examples/unresolved-cfd-dem/liquid-solid-fluidized-bed/liquid-solid-fluidized-bed.rst b/doc/source/examples/unresolved-cfd-dem/liquid-solid-fluidized-bed/liquid-solid-fluidized-bed.rst index 364d06d98b..347009e0a4 100644 --- a/doc/source/examples/unresolved-cfd-dem/liquid-solid-fluidized-bed/liquid-solid-fluidized-bed.rst +++ b/doc/source/examples/unresolved-cfd-dem/liquid-solid-fluidized-bed/liquid-solid-fluidized-bed.rst @@ -332,8 +332,6 @@ We choose the `particle centroid method (PCM) <../../../parameters/unresolved-cf set l2 smoothing factor = 2.8387584e-5 end -.. note:: - Note that void fraction is not bound in this case. The size of the particles used in this example forces us to use a very coarse mesh. Bounding void fraction would lead to instability in the present case. CFD-DEM ~~~~~~~~~~ diff --git a/doc/source/parameters/unresolved-cfd-dem/void-fraction.rst b/doc/source/parameters/unresolved-cfd-dem/void-fraction.rst index c657b20502..5dcd46977f 100644 --- a/doc/source/parameters/unresolved-cfd-dem/void-fraction.rst +++ b/doc/source/parameters/unresolved-cfd-dem/void-fraction.rst @@ -10,9 +10,6 @@ In this subsection, all parameters required for the calculation of the void frac set read dem = true set dem file name = dem set l2 smoothing factor = 0 - set l2 lower bound = 0 - set l2 upper bound = 1 - set bound void fraction = false set particle refinement factor = 0 end @@ -38,8 +35,6 @@ If the ``mode`` chosen is ``pcm``, then the void fraction is calculated using th * The ``read dem`` allows us to read an already existing dem simulation result which can be obtained from checkpointing the Lethe-DEM simulation. This is important as the `lethe-fluid-vans` solver requires reading an initial dem triangulation and particle information to simulate flows in the presence of particles. * The ``dem_file_name`` parameter specifies the prefix of the dem files that must be read. * The ``l2 smoothing factor`` is a smoothing length used for smoothing the L2 projection of the void fraction to avoid sharp discontinuities which can lead to instabilities in the simulation. -* The ``l2 lower bound`` and ``l2 upper bound`` are the minimum and maximum values around which the void fraction is bounded. This is important especially for upper bounds as the void fraction can sometimes slightly exceed a value of 1 when projected. -* The ``bound void fraction`` parameter determines whether or not to bound the void fraction between the lower and upper bounds specified in the previous two parameters. As the void fraction is calculated and then projected using L2 projection, it can sometimes exceeds the maximum value of 1. In order to prevent this, we use an active set method to bound the void fraction. * The ``qcm sphere diameter`` allows us to fix the diameter of all reference spheres in the simulation to a given value. If this option is used (a value other than 0 is specified), it overrides the default calculation of the size of the sphere and sets its diameter to the value specified. * The ``qcm sphere equal cell volume`` determines whether or not we want to use a reference sphere with the same volume as the element in which it is located. If it is disabled, then each sphere will have a radius equal to the size of the element in which it is located. This parameter is important only when the ``qcm sphere diameter`` is not used or is set to 0. * The ``particle refinement factor`` is only required for the ``spm``. It allows to determine the number of pseudo-particles that we want to divide our particle into. By default, it is set to 0 refinements, and results in no refinement of the original meshed particle (division into 7 particles in 3D). Every additional refinement results in a :math:`2^{dim}` times more particles. The figure below shows how the number of pseudo-particles change with every refinement. Every cell in the particle mesh represents a pseudo-particle in the satellite point method. diff --git a/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.prm b/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.prm index 8a641e5496..724b1216bd 100644 --- a/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.prm +++ b/examples/unresolved-cfd-dem/boycott-effect/boycott-effect.prm @@ -94,7 +94,6 @@ subsection void fraction set qcm sphere equal cell volume = false set read dem = true set dem file name = dem - set bound void fraction = false end #--------------------------------------------------- diff --git a/examples/unresolved-cfd-dem/cylindrical-packed-bed/flow-in-bed.prm b/examples/unresolved-cfd-dem/cylindrical-packed-bed/flow-in-bed.prm index 3696989d24..3b4340efc3 100644 --- a/examples/unresolved-cfd-dem/cylindrical-packed-bed/flow-in-bed.prm +++ b/examples/unresolved-cfd-dem/cylindrical-packed-bed/flow-in-bed.prm @@ -55,8 +55,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.00001 - set l2 lower bound = 0 - set l2 upper bound = 1 end #--------------------------------------------------- diff --git a/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.prm b/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.prm index 56fdda5055..d245cc10ef 100644 --- a/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.prm +++ b/examples/unresolved-cfd-dem/gas-solid-fluidized-bed/gas-solid-fluidized-bed.prm @@ -58,9 +58,6 @@ subsection void fraction set read dem = true set dem file name = dem set l2 smoothing factor = 0.000005 - set l2 lower bound = 0 - set l2 upper bound = 1 - set bound void fraction = false end #--------------------------------------------------- diff --git a/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.prm b/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.prm index 8c28a7fa52..b61e9b2a9c 100644 --- a/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.prm +++ b/examples/unresolved-cfd-dem/gas-solid-spouted-bed/gas-solid-spouted-bed.prm @@ -70,7 +70,6 @@ subsection void fraction set qcm sphere equal cell volume = true set read dem = true set dem file name = dem - set bound void fraction = false end #--------------------------------------------------- diff --git a/examples/unresolved-cfd-dem/gas-solid-spouted-cylinder-bed/gas-solid-spouted-cylinder-bed.prm b/examples/unresolved-cfd-dem/gas-solid-spouted-cylinder-bed/gas-solid-spouted-cylinder-bed.prm index bf411ad45d..4afce369f8 100644 --- a/examples/unresolved-cfd-dem/gas-solid-spouted-cylinder-bed/gas-solid-spouted-cylinder-bed.prm +++ b/examples/unresolved-cfd-dem/gas-solid-spouted-cylinder-bed/gas-solid-spouted-cylinder-bed.prm @@ -77,7 +77,6 @@ subsection void fraction set qcm sphere equal cell volume = true set read dem = true set dem file name = dem - set bound void fraction = false end #--------------------------------------------------- diff --git a/include/fem-dem/cfd_dem_simulation_parameters.h b/include/fem-dem/cfd_dem_simulation_parameters.h index 7225cc0033..649cbdceef 100644 --- a/include/fem-dem/cfd_dem_simulation_parameters.h +++ b/include/fem-dem/cfd_dem_simulation_parameters.h @@ -24,8 +24,8 @@ class CFDDEMSimulationParameters SimulationParameters cfd_parameters; DEMSolverParameters dem_parameters; - std::shared_ptr> void_fraction; - Parameters::CFDDEM cfd_dem; + std::shared_ptr> void_fraction; + Parameters::CFDDEM cfd_dem; void declare(ParameterHandler &prm, @@ -34,7 +34,7 @@ class CFDDEMSimulationParameters cfd_parameters.declare(prm, size_of_subsections); dem_parameters.declare(prm); - void_fraction = std::make_shared>(); + void_fraction = std::make_shared>(); void_fraction->declare_parameters(prm); Parameters::CFDDEM::declare_parameters(prm); } diff --git a/include/fem-dem/fluid_dynamics_vans.h b/include/fem-dem/fluid_dynamics_vans.h index 09437dd0d4..1deedcec32 100644 --- a/include/fem-dem/fluid_dynamics_vans.h +++ b/include/fem-dem/fluid_dynamics_vans.h @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -29,63 +30,8 @@ #include #include -using namespace dealii; - -/** - * @brief Calculates the area of intersection between a circular (2D) particle and a circle - * - * @param r_particle Radius of the particle - * - * @param r_circle Radius of the circle - * - * @param neighbor_distance Distance between the particle and the circle - */ -inline double -particle_circle_intersection_2d(double r_particle, - double r_circle, - double neighbor_distance) -{ - return pow(r_particle, 2) * Utilities::fixed_power<-1, double>( - cos((pow(neighbor_distance, 2) + - pow(r_particle, 2) - pow(r_circle, 2)) / - (2 * neighbor_distance * r_particle))) + - Utilities::fixed_power<2, double>(r_circle) * - Utilities::fixed_power<-1, double>( - cos((pow(neighbor_distance, 2) - pow(r_particle, 2) + - pow(r_circle, 2)) / - (2 * neighbor_distance * r_circle))) - - 0.5 * sqrt((-neighbor_distance + r_particle + r_circle) * - (neighbor_distance + r_particle - r_circle) * - (neighbor_distance - r_particle + r_circle) * - (neighbor_distance + r_particle + r_circle)); -} - -/** - * @brief Calculates the volume of intersection between a spherical (3D) particle and a sphere - * - * @param r_particle Radius of the particle - * - * @param r_sphere Radius of the sphere - * - * @param neighbor_distance Distance between the particle and the sphere - */ - -inline double -particle_sphere_intersection_3d(double r_particle, - double r_sphere, - double neighbor_distance) -{ - return M_PI * - Utilities::fixed_power<2, double>(r_sphere + r_particle - - neighbor_distance) * - (Utilities::fixed_power<2, double>(neighbor_distance) + - (2 * neighbor_distance * r_particle) - - (3 * Utilities::fixed_power<2, double>(r_particle)) + - (2 * neighbor_distance * r_sphere) + (6 * r_sphere * r_particle) - - (3 * Utilities::fixed_power<2, double>(r_sphere))) / - (12 * neighbor_distance); -} +using namespace dealii; /** * A solver class for the VANS equation using GLS stabilization @@ -115,79 +61,9 @@ class FluidDynamicsVANS : public FluidDynamicsMatrixBased void update_solution_and_constraints(); - void - particle_centered_method(); - - void - quadrature_centered_sphere_method(bool load_balance_step); - - void - satellite_point_method(); - - void - solve_L2_system_void_fraction(); - void read_dem(); - /** - * @brief This function calculates and returns the periodic offset distance of the domain which is needed - * for the periodic boundary conditions using the QCM or SPM for void fraction - * with the GLS VANS/CFD-DEM solver. The distance is based on one of the - * periodic boundaries and all particle location shifted by this distance is - * according to this periodic boundary. - * - * @param boundary_id The id of one of the periodic boundaries - * - * @return The periodic offset distance - */ - inline Tensor<1, dim> - get_periodic_offset_distance(unsigned int boundary_id) const - { - Tensor<1, dim> offset; - - // Iterating over the active cells in the triangulation - for (const auto &cell : (*this->triangulation).active_cell_iterators()) - { - if (cell->is_locally_owned() || cell->is_ghost()) - { - if (cell->at_boundary()) - { - // Iterating over cell faces - for (unsigned int face_id = 0; face_id < cell->n_faces(); - ++face_id) - { - unsigned int face_boundary_id = - cell->face(face_id)->boundary_id(); - - // Check if face is on the boundary, if so, get - // the periodic offset distance for one pair of periodic - // faces only since periodic boundaries are aligned with the - // direction and only axis are currently allowed - if (face_boundary_id == boundary_id) - { - Point face_center = cell->face(face_id)->center(); - auto periodic_cell = cell->periodic_neighbor(face_id); - unsigned int periodic_face_id = - cell->periodic_neighbor_face_no(face_id); - Point periodic_face_center = - periodic_cell->face(periodic_face_id)->center(); - - offset = periodic_face_center - face_center; - - return offset; - } - } - } - } - } - - // A zero tensor is returned in case no cells are found on the periodic - // boundaries on this processor. This processor won't handle particle in - // cells at periodic boundaries, so it won't affect any computation. - return offset; - } - protected: /** * @brief associates the degrees of freedom to each vertex of the finite elements @@ -200,10 +76,7 @@ class FluidDynamicsVANS : public FluidDynamicsMatrixBased iterate() override; void - initialize_void_fraction(); - - void - calculate_void_fraction(const double time, bool load_balance_step); + calculate_void_fraction(const double time); void vertices_cell_mapping(); @@ -303,8 +176,7 @@ class FluidDynamicsVANS : public FluidDynamicsMatrixBased virtual void output_field_hook(DataOut &data_out) override; - void - percolate_void_fraction(); + /** * Member Variables @@ -312,34 +184,12 @@ class FluidDynamicsVANS : public FluidDynamicsMatrixBased CFDDEMSimulationParameters cfd_dem_simulation_parameters; - DoFHandler void_fraction_dof_handler; - FE_Q fe_void_fraction; - MappingQGeneric particle_mapping; - IndexSet locally_owned_dofs_voidfraction; - IndexSet locally_relevant_dofs_voidfraction; - - // Solution of the void fraction at previous time steps - std::vector previous_void_fraction; - - GlobalVectorType nodal_void_fraction_relevant; - GlobalVectorType nodal_void_fraction_owned; - // Assemblers for the particle_fluid interactions std::vector>> particle_fluid_assemblers; - TrilinosWrappers::SparseMatrix system_matrix_void_fraction; - GlobalVectorType system_rhs_void_fraction; - TrilinosWrappers::SparseMatrix complete_system_matrix_void_fraction; - GlobalVectorType complete_system_rhs_void_fraction; - TrilinosWrappers::SparseMatrix mass_matrix; - GlobalVectorType diagonal_of_mass_matrix; - IndexSet active_set; - - std::shared_ptr ilu_preconditioner; - AffineConstraints void_fraction_constraints; const bool PSPG = true; const bool SUPG = true; @@ -359,6 +209,8 @@ class FluidDynamicsVANS : public FluidDynamicsMatrixBased protected: Particles::ParticleHandler particle_handler; + + VoidFractionBase void_fraction_manager; }; #endif diff --git a/include/fem-dem/parameters_cfd_dem.h b/include/fem-dem/parameters_cfd_dem.h index 3766c66835..2304eadefd 100644 --- a/include/fem-dem/parameters_cfd_dem.h +++ b/include/fem-dem/parameters_cfd_dem.h @@ -49,10 +49,10 @@ namespace Parameters template - class VoidFraction + class VoidFractionParameters { public: - VoidFraction() + VoidFractionParameters() : void_fraction(1) {} @@ -67,11 +67,8 @@ namespace Parameters VoidFractionMode mode; Functions::ParsedFunction void_fraction; bool read_dem; - bool bound_void_fraction; std::string dem_file_name; double l2_smoothing_factor; - double l2_lower_bound; - double l2_upper_bound; unsigned int particle_refinement_factor; double qcm_sphere_diameter; bool qcm_sphere_equal_cell_volume; diff --git a/include/fem-dem/void_fraction.h b/include/fem-dem/void_fraction.h new file mode 100644 index 0000000000..b8d54a0249 --- /dev/null +++ b/include/fem-dem/void_fraction.h @@ -0,0 +1,397 @@ +// SPDX-FileCopyrightText: Copyright (c) 2021-2024 The Lethe Authors +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later + +#ifndef lethe_void_fraction_h +#define lethe_void_fraction_h + +#include + +#include + +#include + +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace dealii; + +/** + * @brief Calculate the area of intersection between a circular (2D) particle and a circle. + * + * @param[in] r_particle Radius of the particle + * + * @param[in] r_circle Radius of the circle + * + * @param[in] neighbor_distance Distance between the particle and the circle + */ +inline double +particle_circle_intersection_2d(double r_particle, + double r_circle, + double neighbor_distance) +{ + return pow(r_particle, 2) * Utilities::fixed_power<-1, double>( + cos((pow(neighbor_distance, 2) + + pow(r_particle, 2) - pow(r_circle, 2)) / + (2 * neighbor_distance * r_particle))) + + Utilities::fixed_power<2, double>(r_circle) * + Utilities::fixed_power<-1, double>( + cos((pow(neighbor_distance, 2) - pow(r_particle, 2) + + pow(r_circle, 2)) / + (2 * neighbor_distance * r_circle))) - + 0.5 * sqrt((-neighbor_distance + r_particle + r_circle) * + (neighbor_distance + r_particle - r_circle) * + (neighbor_distance - r_particle + r_circle) * + (neighbor_distance + r_particle + r_circle)); +} + +/** + * @brief Calculate the volume of intersection between a spherical (3D) particle and a sphere. + * + * @param[in] r_particle Radius of the particle + * + * @param[in] r_sphere Radius of the sphere + * + * @param[in] neighbor_distance Distance between the particle and the sphere + */ + +inline double +particle_sphere_intersection_3d(double r_particle, + double r_sphere, + double neighbor_distance) +{ + return M_PI * + Utilities::fixed_power<2, double>(r_sphere + r_particle - + neighbor_distance) * + (Utilities::fixed_power<2, double>(neighbor_distance) + + (2 * neighbor_distance * r_particle) - + (3 * Utilities::fixed_power<2, double>(r_particle)) + + (2 * neighbor_distance * r_sphere) + (6 * r_sphere * r_particle) - + (3 * Utilities::fixed_power<2, double>(r_sphere))) / + (12 * neighbor_distance); +} + +/** + * @brief Void fraction calculator. This class manages the calculation of the + * void fraction which is used as an auxiliary field for the solvers that solve + * the Volume-Averaged Navier-Stokes equations. The present architecture + * of the class support all of the different void fraction calculation methods + * within a single class instead of building a class hierarchy. This is + * because there are only a few void fraction calculation strategies. + * + * @tparam dim An integer that denotes the number of spatial dimensions. + */ +template +class VoidFractionBase : public PhysicsLinearSubequationsSolver +{ +public: + /** + * @brief Constructor for the void fraction calculator. + * + * @param triangulation The triangulation on which the simulation is being done. + * @param input_parameters The parameters for the void fraction calculation. + * @param linear_solver_parameters The parameters for the linear solver. This is used to solve the L2 projection of the void fraction + * @param particle_handler The particle handler used to manage the particles in the simulation. This object must be provided even if the particles are not used to establish the void fraction. + * @param fe_degree The finite element degree used to interpolate the void fraction. + * @param simplex A flag to indicate if the simulations are being done with simplex elements. + * @param pcout The ConditionalOStream used to print the information. + */ + VoidFractionBase( + parallel::DistributedTriangulationBase *triangulation, + std::shared_ptr> input_parameters, + const Parameters::LinearSolver &linear_solver_parameters, + Particles::ParticleHandler *particle_handler, + const unsigned int fe_degree, + const bool simplex, + const ConditionalOStream &pcout) + : PhysicsLinearSubequationsSolver(pcout) + , dof_handler(*triangulation) + , triangulation(triangulation) + , void_fraction_parameters(input_parameters) + , linear_solver_parameters(linear_solver_parameters) + , particle_handler(particle_handler) + { + if (simplex) + { + fe = std::make_shared>(fe_degree); + mapping = std::make_shared>(*fe); + quadrature = std::make_shared>(fe->degree + 1); + } + else + { + // Usual case, for quad/hex meshes + fe = std::make_shared>(fe_degree); + mapping = std::make_shared>(fe->degree); + quadrature = std::make_shared>(fe->degree + 1); + } + } + + /** + * @brief Setup the degrees of freedom. + * + */ + void + setup_dofs() override; + + + /** + * @brief Establish the constraints of the void fraction systems. + * + * @param[in] boundary_conditions The boundary conditions of fluid dynamics. + * This is used to establish periodic boundary conditions for the void + * fraction. + * + */ + void + setup_constraints( + const BoundaryConditions::NSBoundaryConditions &boundary_conditions); + + + /** + * @brief Calculate the void fraction. + * + * @param[in] time Current time for which the void fraction is to be + * calculated. + * + */ + void + calculate_void_fraction(const double time); + + + /** + * @brief Assemble and solve the system. + * + * @param[in] is_post_mesh_adaptation Indicates if the equation is being + * solved during post_mesh_adaptation(), for verbosity. + */ + void + solve(const bool & /*is_post_mesh_adaptation*/) override + {} + + + /** + * @brief Percolate the time vector for the void fraction. This operation is called at the end of a time-step. + * + */ + void + percolate_void_fraction() + { + for (unsigned int i = previous_void_fraction.size() - 1; i > 0; --i) + { + previous_void_fraction[i] = previous_void_fraction[i - 1]; + } + previous_void_fraction[0] = void_fraction_locally_relevant; + } + + + /** + * @brief Initialize the void fraction at the beginning of a simulation. + * + * @param[in] time The current time. This is used for time-dependent + * functions. + */ + void + initialize_void_fraction(const double time) + { + calculate_void_fraction(time); + for (auto &previous_solution : this->previous_void_fraction) + previous_solution = void_fraction_locally_relevant; + } + + /// DoFHandler that manages the void fraction + DoFHandler dof_handler; + + /// The solutions are made public instead of using getters + /// Solution of the void fraction at previous time steps + std::vector previous_void_fraction; + + /// Fully distributed (including locally relevant) solution + GlobalVectorType void_fraction_locally_relevant; + + /// Finite element for the void fraction + std::shared_ptr> fe; + + /// Index set for the locally owned degree of freedoms + IndexSet locally_owned_dofs; + + /// Index set for the locally relevant degree of freedoms + IndexSet locally_relevant_dofs; + + /// Constraints used for the boundary conditions of the void fraction. + /// Currently, this is only used to establish periodic void fractions. This + /// object has to be made public because the boundary conditions are set + /// outside of the object for now. + AffineConstraints void_fraction_constraints; + + +private: + /** + * @brief Calculate the void fraction using a function. This is a straightforward usage of VectorTools. + * + * @param[in] time Current time for which the void fraction is to be + * calculated. + * + */ + void + calculate_void_fraction_function(const double time); + + /** + * @brief Calculate the void fraction using the particle centered method. + * + */ + void + calculate_void_fraction_particle_centered_method(); + + /** + * @brief Calculate the void fraction using the satellite point method. + * + */ + void + calculate_void_fraction_satellite_point_method(); + + /** + * @brief Calculate the void fraction using the Quadrature-Centered Method (QCM). + * + */ + void + calculate_void_fraction_quadrature_centered_method(); + + /** + * @brief Solve the linear system resulting from the assemblies. + * + * @param[in] is_post_mesh_adaptation Indicates if the equation is being + * solved during post_mesh_adaptation(), for verbosity. + */ + virtual void + solve_linear_system_and_update_solution( + const bool &is_post_mesh_adaptation = false) override; + + /** + * @brief Calculate and return the periodic offset distance vector of the domain which is needed + * for the periodic boundary conditions using the QCM or SPM for void fraction + * with the GLS VANS/CFD-DEM solver. The distance is based on one of the + * periodic boundaries. This periodic boundary is then used as the reference + * for all particle locations shifted by this offset vector. + * + * @param[in] boundary_id The ID of one of the periodic boundaries + * + * @return The periodic offset vector. + */ + inline Tensor<1, dim> + get_periodic_offset_distance(unsigned int boundary_id) const + { + Tensor<1, dim> offset; + + // Iterating over the active cells in the triangulation + for (const auto &cell : (*this->triangulation).active_cell_iterators()) + { + if (cell->is_locally_owned() || cell->is_ghost()) + { + if (cell->at_boundary()) + { + // Iterating over cell faces + for (unsigned int face_id = 0; face_id < cell->n_faces(); + ++face_id) + { + unsigned int face_boundary_id = + cell->face(face_id)->boundary_id(); + + // Check if face is on the boundary, if so, get + // the periodic offset distance for one pair of periodic + // faces only since periodic boundaries are aligned with the + // direction and only axis are currently allowed + if (face_boundary_id == boundary_id) + { + Point face_center = cell->face(face_id)->center(); + auto periodic_cell = cell->periodic_neighbor(face_id); + unsigned int periodic_face_id = + cell->periodic_neighbor_face_no(face_id); + Point periodic_face_center = + periodic_cell->face(periodic_face_id)->center(); + + offset = periodic_face_center - face_center; + + return offset; + } + } + } + } + } + + // A zero tensor is returned in case no cells are found on the periodic + // boundaries on this processor. This processor won't handle particle in + // cells at periodic boundaries, so it won't affect any computation. + return offset; + } + + /// Triangulation + parallel::DistributedTriangulationBase *triangulation; + + /// Mapping for the void fraction + std::shared_ptr> mapping; + + /// Quadrature for the void fraction + std::shared_ptr> quadrature; + + /// Parameters for the calculation of the void fraction + std::shared_ptr> + void_fraction_parameters; + + /// Linear solvers for the calculation of the void fraction + const Parameters::LinearSolver linear_solver_parameters; + + /// Particle handler used when the void fraction depends on particles + Particles::ParticleHandler *particle_handler; + + /// Locally owned solution of the void fraction + GlobalVectorType void_fraction_locally_owned; + + /// Preconditioner used for the solution of the smoothed L2 projection of the + /// void fraction + std::shared_ptr ilu_preconditioner; + + /// System matrix used to assemble the smoothed L2 projection of the void + /// fraction + TrilinosWrappers::SparseMatrix system_matrix_void_fraction; + + /// Right-hand side used to assemble the smoothed L2 projection of the void + /// fraction + GlobalVectorType system_rhs_void_fraction; + + /// Vertices to cell map, this is used in the QCM and the satellite point + /// method to access the neighboring cells of a cell. + std::map::active_cell_iterator>> + vertices_to_cell; + + /// Boolean to indicate if the mesh has periodic boundaries + bool has_periodic_boundaries; + + /// Offset for the periodic boundary condition + Tensor<1, dim> periodic_offset; + + /// Direction associated with the periodic boundary condition + unsigned int periodic_direction; + + /// Vertices to periodic cell map, this is used in the QCM and the satellite + /// point method to access the neighboring cells of a cell. + std::map::active_cell_iterator>> + vertices_to_periodic_cell; +}; + + + +#endif diff --git a/source/fem-dem/CMakeLists.txt b/source/fem-dem/CMakeLists.txt index a4f4a42a6b..6c15fbb49d 100644 --- a/source/fem-dem/CMakeLists.txt +++ b/source/fem-dem/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(lethe-fem-dem parameters_cfd_dem.cc postprocessing_cfd_dem.cc vans_assemblers.cc + void_fraction.cc # Headers ../../include/fem-dem/cfd_dem_coupling.h ../../include/fem-dem/cfd_dem_simulation_parameters.h @@ -16,7 +17,8 @@ add_library(lethe-fem-dem ../../include/fem-dem/ib_particles_dem.h ../../include/fem-dem/parameters_cfd_dem.h ../../include/fem-dem/postprocessing_cfd_dem.h - ../../include/fem-dem/vans_assemblers.h) + ../../include/fem-dem/vans_assemblers.h + ../../include/fem-dem/void_fraction.h) deal_ii_setup_target(lethe-fem-dem) target_link_libraries(lethe-fem-dem lethe-core lethe-dem lethe-solvers) diff --git a/source/fem-dem/cfd_dem_coupling.cc b/source/fem-dem/cfd_dem_coupling.cc index 58b9a330c5..aa35605154 100644 --- a/source/fem-dem/cfd_dem_coupling.cc +++ b/source/fem-dem/cfd_dem_coupling.cc @@ -218,7 +218,7 @@ CFDDEMSolver::initialize_dem_parameters() // Set up the local and ghost cells (if ASC enabled) sparse_contacts_object.update_local_and_ghost_cell_set( - this->void_fraction_dof_handler); + this->void_fraction_manager.dof_handler); particle_wall_contact_force_object = set_particle_wall_contact_force_model( this->cfd_dem_simulation_parameters.dem_parameters, @@ -268,7 +268,7 @@ CFDDEMSolver::initialize_dem_parameters() dem_action_manager->check_sparse_contacts_enabled()) { sparse_contacts_object.map_periodic_nodes( - this->void_fraction_constraints); + this->void_fraction_manager.void_fraction_constraints); } this->pcout << "Finished initializing DEM parameters" << std::endl @@ -412,17 +412,21 @@ CFDDEMSolver::write_checkpoint() // Void Fraction std::vector vf_set_transfer; - vf_set_transfer.push_back(&this->nodal_void_fraction_relevant); - for (unsigned int i = 0; i < this->previous_void_fraction.size(); ++i) + vf_set_transfer.push_back( + &this->void_fraction_manager.void_fraction_locally_relevant); + for (unsigned int i = 0; + i < this->void_fraction_manager.previous_void_fraction.size(); + ++i) { - vf_set_transfer.push_back(&this->previous_void_fraction[i]); + vf_set_transfer.push_back( + &this->void_fraction_manager.previous_void_fraction[i]); } this->multiphysics->write_checkpoint(); // Prepare for Serialization parallel::distributed::SolutionTransfer - vf_system_trans_vectors(this->void_fraction_dof_handler); + vf_system_trans_vectors(this->void_fraction_manager.dof_handler); vf_system_trans_vectors.prepare_for_serialization(vf_set_transfer); if (auto parallel_triangulation = @@ -492,12 +496,13 @@ CFDDEMSolver::read_checkpoint() this->setup_dofs(); + // TODO BB // Remap periodic nodes after setup of dofs if (dem_action_manager->check_periodic_boundaries_enabled() && dem_action_manager->check_sparse_contacts_enabled()) { sparse_contacts_object.map_periodic_nodes( - this->void_fraction_constraints); + this->void_fraction_manager.void_fraction_constraints); } // Velocity Vectors @@ -540,35 +545,41 @@ CFDDEMSolver::read_checkpoint() // Void Fraction Vectors std::vector vf_system( - 1 + this->previous_void_fraction.size()); + 1 + this->void_fraction_manager.previous_void_fraction.size()); - GlobalVectorType vf_distributed_system(this->locally_owned_dofs_voidfraction, - this->mpi_communicator); + GlobalVectorType vf_distributed_system( + this->void_fraction_manager.locally_owned_dofs, this->mpi_communicator); vf_system[0] = &(vf_distributed_system); std::vector vf_distributed_previous_solutions; vf_distributed_previous_solutions.reserve( - this->previous_void_fraction.size()); + this->void_fraction_manager.previous_void_fraction.size()); - for (unsigned int i = 0; i < this->previous_void_fraction.size(); ++i) + for (unsigned int i = 0; + i < this->void_fraction_manager.previous_void_fraction.size(); + ++i) { vf_distributed_previous_solutions.emplace_back( - GlobalVectorType(this->locally_owned_dofs_voidfraction, + GlobalVectorType(this->void_fraction_manager.locally_owned_dofs, this->mpi_communicator)); vf_system[i + 1] = &vf_distributed_previous_solutions[i]; } parallel::distributed::SolutionTransfer - vf_system_trans_vectors(this->void_fraction_dof_handler); + vf_system_trans_vectors(this->void_fraction_manager.dof_handler); vf_system_trans_vectors.deserialize(vf_system); - this->nodal_void_fraction_relevant = vf_distributed_system; - for (unsigned int i = 0; i < this->previous_void_fraction.size(); ++i) + this->void_fraction_manager.void_fraction_locally_relevant = + vf_distributed_system; + for (unsigned int i = 0; + i < this->void_fraction_manager.previous_void_fraction.size(); + ++i) { - this->previous_void_fraction[i] = vf_distributed_previous_solutions[i]; + this->void_fraction_manager.previous_void_fraction[i] = + vf_distributed_previous_solutions[i]; } if (this->simulation_parameters.flow_control.enable_flow_control) @@ -642,15 +653,19 @@ CFDDEMSolver::load_balance() // Void Fraction std::vector vf_set_transfer; - vf_set_transfer.push_back(&this->nodal_void_fraction_relevant); - for (unsigned int i = 0; i < this->previous_void_fraction.size(); ++i) + vf_set_transfer.push_back( + &this->void_fraction_manager.void_fraction_locally_relevant); + for (unsigned int i = 0; + i < this->void_fraction_manager.previous_void_fraction.size(); + ++i) { - vf_set_transfer.push_back(&this->previous_void_fraction[i]); + vf_set_transfer.push_back( + &this->void_fraction_manager.previous_void_fraction[i]); } // Prepare for Serialization parallel::distributed::SolutionTransfer - vf_system_trans_vectors(this->void_fraction_dof_handler); + vf_system_trans_vectors(this->void_fraction_manager.dof_handler); vf_system_trans_vectors.prepare_for_coarsening_and_refinement( vf_set_transfer); @@ -704,17 +719,18 @@ CFDDEMSolver::load_balance() this->pcout << "Setup DOFs" << std::endl; this->setup_dofs(); + // TODO BB // Remap periodic nodes after setup of dofs if (dem_action_manager->check_periodic_boundaries_enabled() && dem_action_manager->check_sparse_contacts_enabled()) { sparse_contacts_object.map_periodic_nodes( - this->void_fraction_constraints); + this->void_fraction_manager.void_fraction_constraints); } // Update the local and ghost cells (if ASC enabled) sparse_contacts_object.update_local_and_ghost_cell_set( - this->void_fraction_dof_handler); + this->void_fraction_manager.dof_handler); // Velocity Vectors std::vector x_system(1 + this->previous_solutions.size()); @@ -747,32 +763,38 @@ CFDDEMSolver::load_balance() // Void Fraction Vectors std::vector vf_system( - 1 + this->previous_void_fraction.size()); + 1 + this->void_fraction_manager.previous_void_fraction.size()); - GlobalVectorType vf_distributed_system(this->locally_owned_dofs_voidfraction, - this->mpi_communicator); + GlobalVectorType vf_distributed_system( + this->void_fraction_manager.locally_owned_dofs, this->mpi_communicator); vf_system[0] = &(vf_distributed_system); std::vector vf_distributed_previous_solutions; vf_distributed_previous_solutions.reserve( - this->previous_void_fraction.size()); + this->void_fraction_manager.previous_void_fraction.size()); - for (unsigned int i = 0; i < this->previous_void_fraction.size(); ++i) + for (unsigned int i = 0; + i < this->void_fraction_manager.previous_void_fraction.size(); + ++i) { vf_distributed_previous_solutions.emplace_back( - GlobalVectorType(this->locally_owned_dofs_voidfraction, + GlobalVectorType(this->void_fraction_manager.locally_owned_dofs, this->mpi_communicator)); vf_system[i + 1] = &vf_distributed_previous_solutions[i]; } vf_system_trans_vectors.interpolate(vf_system); - this->nodal_void_fraction_relevant = vf_distributed_system; - for (unsigned int i = 0; i < this->previous_void_fraction.size(); ++i) + this->void_fraction_manager.void_fraction_locally_relevant = + vf_distributed_system; + for (unsigned int i = 0; + i < this->void_fraction_manager.previous_void_fraction.size(); + ++i) { - this->previous_void_fraction[i] = vf_distributed_previous_solutions[i]; + this->void_fraction_manager.previous_void_fraction[i] = + vf_distributed_previous_solutions[i]; } vf_system.clear(); @@ -1084,10 +1106,11 @@ CFDDEMSolver::postprocess_cfd_dem() TimerOutput::Scope t(this->computing_timer, "total_volume_calculation"); double total_volume_fluid, total_volume_particles; std::tie(total_volume_fluid, total_volume_particles) = - calculate_fluid_and_particle_volumes(this->void_fraction_dof_handler, - this->nodal_void_fraction_relevant, - *this->cell_quadrature, - *this->mapping); + calculate_fluid_and_particle_volumes( + this->void_fraction_manager.dof_handler, + this->void_fraction_manager.void_fraction_locally_relevant, + *this->cell_quadrature, + *this->mapping); this->table_phase_volumes.add_value( "time", this->simulation_control->get_current_time()); this->table_phase_volumes.add_value("total-volume-fluid", @@ -1144,14 +1167,14 @@ CFDDEMSolver::dynamic_flow_control() // Calculate the average velocity according to the void fraction unsigned int flow_direction = this->simulation_parameters.flow_control.flow_direction; - double average_velocity = - calculate_average_velocity(this->dof_handler, - this->void_fraction_dof_handler, - this->present_solution, - this->nodal_void_fraction_relevant, - flow_direction, - *this->cell_quadrature, - *this->mapping); + double average_velocity = calculate_average_velocity( + this->dof_handler, + this->void_fraction_manager.dof_handler, + this->present_solution, + this->void_fraction_manager.void_fraction_locally_relevant, + flow_direction, + *this->cell_quadrature, + *this->mapping); // Calculate the beta force for fluid this->flow_control.calculate_beta( @@ -1333,7 +1356,7 @@ CFDDEMSolver::dem_contact_build(unsigned int counter) // Identify the mobility status of particles (if ASC enabled) sparse_contacts_object.identify_mobility_status( - this->void_fraction_dof_handler, + this->void_fraction_manager.dof_handler, this->particle_handler, (*this->triangulation).n_active_cells(), this->mpi_communicator); @@ -1420,7 +1443,8 @@ CFDDEMSolver::solve() // Calculate first instance of void fraction once particles are set-up this->vertices_cell_mapping(); if (!dem_action_manager->check_restart_simulation()) - this->initialize_void_fraction(); + this->void_fraction_manager.initialize_void_fraction( + this->simulation_control->get_current_time()); while (this->simulation_control->integrate()) { @@ -1440,8 +1464,7 @@ CFDDEMSolver::solve() } this->calculate_void_fraction( - this->simulation_control->get_current_time(), - dem_action_manager->check_load_balance()); + this->simulation_control->get_current_time()); this->iterate(); if (this->cfd_dem_simulation_parameters.cfd_parameters.test.enabled) @@ -1468,7 +1491,7 @@ CFDDEMSolver::solve() // Output mobility status vector visualization_object.print_intermediate_format( mobility_status, - this->void_fraction_dof_handler, + this->void_fraction_manager.dof_handler, this->mpi_communicator); } break; diff --git a/source/fem-dem/fluid_dynamics_vans.cc b/source/fem-dem/fluid_dynamics_vans.cc index ce5efab0c4..af581d4a7e 100644 --- a/source/fem-dem/fluid_dynamics_vans.cc +++ b/source/fem-dem/fluid_dynamics_vans.cc @@ -4,6 +4,7 @@ #include #include +#include #include @@ -17,15 +18,21 @@ FluidDynamicsVANS::FluidDynamicsVANS( CFDDEMSimulationParameters &nsparam) : FluidDynamicsMatrixBased(nsparam.cfd_parameters) , cfd_dem_simulation_parameters(nsparam) - , void_fraction_dof_handler(*this->triangulation) - , fe_void_fraction(nsparam.cfd_parameters.fem_parameters.void_fraction_order) , particle_mapping(1) , particle_handler(*this->triangulation, particle_mapping, DEM::get_number_properties()) + , void_fraction_manager( + &(*this->triangulation), + nsparam.void_fraction, + this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver.at( + PhysicsID::fluid_dynamics), + &particle_handler, + this->cfd_dem_simulation_parameters.cfd_parameters.fem_parameters + .void_fraction_order, + this->cfd_dem_simulation_parameters.cfd_parameters.mesh.simplex, + this->pcout) { - previous_void_fraction.resize(maximum_number_of_previous_solutions()); - unsigned int n_pbc = 0; for (auto const &[id, type] : cfd_dem_simulation_parameters.cfd_parameters.boundary_conditions.type) @@ -49,7 +56,6 @@ template FluidDynamicsVANS::~FluidDynamicsVANS() { this->dof_handler.clear(); - void_fraction_dof_handler.clear(); } template @@ -58,107 +64,9 @@ FluidDynamicsVANS::setup_dofs() { FluidDynamicsMatrixBased::setup_dofs(); - void_fraction_dof_handler.distribute_dofs(fe_void_fraction); - locally_owned_dofs_voidfraction = - void_fraction_dof_handler.locally_owned_dofs(); - - locally_relevant_dofs_voidfraction = - DoFTools::extract_locally_relevant_dofs(void_fraction_dof_handler); - - void_fraction_constraints.clear(); - void_fraction_constraints.reinit(locally_relevant_dofs_voidfraction); - DoFTools::make_hanging_node_constraints(void_fraction_dof_handler, - void_fraction_constraints); - - // Define constraints for periodic boundary conditions - auto &boundary_conditions = - this->cfd_dem_simulation_parameters.cfd_parameters.boundary_conditions; - for (auto const &[id, type] : boundary_conditions.type) - { - if (type == BoundaryConditions::BoundaryType::periodic) - { - periodic_direction = boundary_conditions.periodic_direction.at(id); - DoFTools::make_periodicity_constraints( - void_fraction_dof_handler, - id, - boundary_conditions.periodic_neighbor_id.at(id), - periodic_direction, - void_fraction_constraints); - - // Get periodic offset if void fraction method is qcm or spm - if (this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::qcm || - this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::spm) - { - periodic_offset = get_periodic_offset_distance(id); - } - } - } - - void_fraction_constraints.close(); - - nodal_void_fraction_relevant.reinit(locally_owned_dofs_voidfraction, - locally_relevant_dofs_voidfraction, - this->mpi_communicator); - - // Initialize vector of previous solutions for the void fraction - for (auto &solution : this->previous_void_fraction) - { - solution.reinit(this->locally_owned_dofs_voidfraction, - this->locally_relevant_dofs_voidfraction, - this->mpi_communicator); - } - - nodal_void_fraction_owned.reinit(locally_owned_dofs_voidfraction, - this->mpi_communicator); - - DynamicSparsityPattern dsp(locally_relevant_dofs_voidfraction); - DoFTools::make_sparsity_pattern(void_fraction_dof_handler, - dsp, - void_fraction_constraints, - false); - SparsityTools::distribute_sparsity_pattern( - dsp, - locally_owned_dofs_voidfraction, - this->mpi_communicator, - locally_relevant_dofs_voidfraction); - - system_matrix_void_fraction.reinit(locally_owned_dofs_voidfraction, - locally_owned_dofs_voidfraction, - dsp, - this->mpi_communicator); - - complete_system_matrix_void_fraction.reinit(locally_owned_dofs_voidfraction, - locally_owned_dofs_voidfraction, - dsp, - this->mpi_communicator); - - system_rhs_void_fraction.reinit(locally_owned_dofs_voidfraction, - this->mpi_communicator); - - complete_system_rhs_void_fraction.reinit(locally_owned_dofs_voidfraction, - this->mpi_communicator); - - active_set.set_size(void_fraction_dof_handler.n_dofs()); - - mass_matrix.reinit(locally_owned_dofs_voidfraction, - locally_owned_dofs_voidfraction, - dsp, - this->mpi_communicator); - - assemble_mass_matrix_diagonal(mass_matrix); -} - -template -void -FluidDynamicsVANS::percolate_void_fraction() -{ - for (unsigned int i = previous_void_fraction.size() - 1; i > 0; --i) - { - previous_void_fraction[i] = previous_void_fraction[i - 1]; - } - previous_void_fraction[0] = nodal_void_fraction_relevant; + void_fraction_manager.setup_dofs(); + void_fraction_manager.setup_constraints( + this->cfd_dem_simulation_parameters.cfd_parameters.boundary_conditions); } template @@ -167,7 +75,7 @@ FluidDynamicsVANS::finish_time_step_fd() { // Void fraction percolation must be done before the time step is finished to // ensure that the checkpointed information is correct - percolate_void_fraction(); + void_fraction_manager.percolate_void_fraction(); FluidDynamicsMatrixBased::finish_time_step(); } @@ -243,198 +151,10 @@ FluidDynamicsVANS::read_dem() template void -FluidDynamicsVANS::initialize_void_fraction() -{ - calculate_void_fraction(this->simulation_control->get_current_time(), false); - for (auto &previous_solution : this->previous_void_fraction) - previous_solution = nodal_void_fraction_relevant; -} - -template -void -FluidDynamicsVANS::calculate_void_fraction(const double time, - bool load_balance_step) +FluidDynamicsVANS::calculate_void_fraction(const double time) { - announce_string(this->pcout, "Void Fraction"); - TimerOutput::Scope t(this->computing_timer, "Calculate void fraction"); - if (this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::function) - { - const MappingQ mapping(1); - - this->cfd_dem_simulation_parameters.void_fraction->void_fraction.set_time( - time); - - VectorTools::interpolate( - mapping, - void_fraction_dof_handler, - this->cfd_dem_simulation_parameters.void_fraction->void_fraction, - nodal_void_fraction_owned); - - nodal_void_fraction_relevant = nodal_void_fraction_owned; - } - else - { - if (this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::pcm) - particle_centered_method(); - else if (this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::qcm) - quadrature_centered_sphere_method(load_balance_step); - else if (this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::spm) - satellite_point_method(); - - solve_L2_system_void_fraction(); - if (this->cfd_dem_simulation_parameters.void_fraction - ->bound_void_fraction == true) - update_solution_and_constraints(); - } -} - -template -void -FluidDynamicsVANS::assemble_mass_matrix_diagonal( - TrilinosWrappers::SparseMatrix &mass_matrix) -{ - Assert(fe_void_fraction.degree == 1, ExcNotImplemented()); - QGauss quadrature_formula(this->number_quadrature_points); - FEValues fe_void_fraction_values(fe_void_fraction, - quadrature_formula, - update_values | update_JxW_values); - const unsigned int dofs_per_cell = fe_void_fraction.dofs_per_cell; - const unsigned int n_qpoints = quadrature_formula.size(); - FullMatrix cell_matrix(dofs_per_cell, dofs_per_cell); - std::vector local_dof_indices(dofs_per_cell); - for (const auto &cell : void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - fe_void_fraction_values.reinit(cell); - cell_matrix = 0; - for (unsigned int q = 0; q < n_qpoints; ++q) - for (unsigned int i = 0; i < dofs_per_cell; ++i) - cell_matrix(i, i) += (fe_void_fraction_values.shape_value(i, q) * - fe_void_fraction_values.shape_value(i, q) * - fe_void_fraction_values.JxW(q)); - cell->get_dof_indices(local_dof_indices); - void_fraction_constraints.distribute_local_to_global( - cell_matrix, local_dof_indices, mass_matrix); - } - } -} - -template -void -FluidDynamicsVANS::update_solution_and_constraints() -{ - const double penalty_parameter = 100; - - GlobalVectorType lambda(locally_owned_dofs_voidfraction, - this->mpi_communicator); - - nodal_void_fraction_owned = nodal_void_fraction_relevant; - - complete_system_matrix_void_fraction.residual(lambda, - nodal_void_fraction_owned, - system_rhs_void_fraction); - - void_fraction_constraints.clear(); - - // reinitialize affine constraints - void_fraction_constraints.reinit(locally_relevant_dofs_voidfraction); - - // Remake hanging node constraints - DoFTools::make_hanging_node_constraints(void_fraction_dof_handler, - void_fraction_constraints); - - // Define constraints for periodic boundary conditions - auto &boundary_conditions = - this->cfd_dem_simulation_parameters.cfd_parameters.boundary_conditions; - for (auto const &[id, type] : boundary_conditions.type) - { - if (type == BoundaryConditions::BoundaryType::periodic) - { - periodic_direction = boundary_conditions.periodic_direction.at(id); - DoFTools::make_periodicity_constraints( - void_fraction_dof_handler, - id, - boundary_conditions.periodic_neighbor_id.at(id), - periodic_direction, - void_fraction_constraints); - - // Get periodic offset if void fraction method is qcm or spm - if (this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::qcm || - this->cfd_dem_simulation_parameters.void_fraction->mode == - Parameters::VoidFractionMode::spm) - { - periodic_offset = get_periodic_offset_distance(id); - } - } - } - - active_set.clear(); - - for (const auto &cell : void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - for (unsigned int v = 0; v < GeometryInfo::vertices_per_cell; - ++v) - { - Assert(void_fraction_dof_handler.get_fe().dofs_per_cell == - GeometryInfo::vertices_per_cell, - ExcNotImplemented()); - const unsigned int dof_index = cell->vertex_dof_index(v, 0); - if (locally_owned_dofs_voidfraction.is_element(dof_index)) - { - const double solution_value = - nodal_void_fraction_owned(dof_index); - if (lambda(dof_index) + - penalty_parameter * mass_matrix(dof_index, dof_index) * - (solution_value - this->cfd_dem_simulation_parameters - .void_fraction->l2_upper_bound) > - 0) - { - active_set.add_index(dof_index); - void_fraction_constraints.add_line(dof_index); - void_fraction_constraints.set_inhomogeneity( - dof_index, - this->cfd_dem_simulation_parameters.void_fraction - ->l2_upper_bound); - nodal_void_fraction_owned(dof_index) = - this->cfd_dem_simulation_parameters.void_fraction - ->l2_upper_bound; - lambda(dof_index) = 0; - } - else if (lambda(dof_index) + - penalty_parameter * - mass_matrix(dof_index, dof_index) * - (solution_value - - this->cfd_dem_simulation_parameters - .void_fraction->l2_lower_bound) < - 0) - { - active_set.add_index(dof_index); - void_fraction_constraints.add_line(dof_index); - void_fraction_constraints.set_inhomogeneity( - dof_index, - this->cfd_dem_simulation_parameters.void_fraction - ->l2_lower_bound); - nodal_void_fraction_owned(dof_index) = - this->cfd_dem_simulation_parameters.void_fraction - ->l2_lower_bound; - lambda(dof_index) = 0; - } - } - } - } - } - active_set.compress(); - nodal_void_fraction_relevant = nodal_void_fraction_owned; - void_fraction_constraints.close(); + void_fraction_manager.calculate_void_fraction(time); } template @@ -444,963 +164,12 @@ FluidDynamicsVANS::vertices_cell_mapping() // Find all the cells around each vertex TimerOutput::Scope t(this->computing_timer, "Map vertices to cell"); - LetheGridTools::vertices_cell_mapping(this->void_fraction_dof_handler, + LetheGridTools::vertices_cell_mapping(this->void_fraction_manager.dof_handler, vertices_to_cell); if (has_periodic_boundaries) LetheGridTools::vertices_cell_mapping_with_periodic_boundaries( - this->void_fraction_dof_handler, vertices_to_periodic_cell); -} - -template -void -FluidDynamicsVANS::particle_centered_method() -{ - QGauss quadrature_formula(this->number_quadrature_points); - const MappingQ mapping(1); - - FEValues fe_values_void_fraction(mapping, - this->fe_void_fraction, - quadrature_formula, - update_values | - update_quadrature_points | - update_JxW_values | update_gradients); - - const unsigned int dofs_per_cell = this->fe_void_fraction.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - FullMatrix local_matrix_void_fraction(dofs_per_cell, dofs_per_cell); - Vector local_rhs_void_fraction(dofs_per_cell); - std::vector local_dof_indices(dofs_per_cell); - std::vector phi_vf(dofs_per_cell); - std::vector> grad_phi_vf(dofs_per_cell); - - system_rhs_void_fraction = 0; - system_matrix_void_fraction = 0; - - for (const auto &cell : - this->void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - fe_values_void_fraction.reinit(cell); - - local_matrix_void_fraction = 0; - local_rhs_void_fraction = 0; - - double solid_volume_in_cell = 0; - - // Loop over particles in cell - // Begin and end iterator for particles in cell - const auto pic = particle_handler.particles_in_cell(cell); - for (auto &particle : pic) - { - auto particle_properties = particle.get_properties(); - solid_volume_in_cell += - M_PI * - Utilities::fixed_power( - particle_properties[DEM::PropertiesIndex::dp]) / - (2.0 * dim); - } - double cell_volume = compute_cell_measure_with_JxW( - fe_values_void_fraction.get_JxW_values()); - - // Calculate cell void fraction - double cell_void_fraction = - (cell_volume - solid_volume_in_cell) / cell_volume; - - for (unsigned int q = 0; q < n_q_points; ++q) - { - for (unsigned int k = 0; k < dofs_per_cell; ++k) - { - phi_vf[k] = fe_values_void_fraction.shape_value(k, q); - grad_phi_vf[k] = fe_values_void_fraction.shape_grad(k, q); - } - for (unsigned int i = 0; i < dofs_per_cell; ++i) - { - // Assemble L2 projection - // Matrix assembly - for (unsigned int j = 0; j < dofs_per_cell; ++j) - { - local_matrix_void_fraction(i, j) += - (phi_vf[j] * phi_vf[i]) * - fe_values_void_fraction.JxW(q) + - (this->cfd_dem_simulation_parameters.void_fraction - ->l2_smoothing_factor * - grad_phi_vf[j] * grad_phi_vf[i] * - fe_values_void_fraction.JxW(q)); - } - local_rhs_void_fraction(i) += phi_vf[i] * cell_void_fraction * - fe_values_void_fraction.JxW(q); - } - } - cell->get_dof_indices(local_dof_indices); - void_fraction_constraints.distribute_local_to_global( - local_matrix_void_fraction, - local_rhs_void_fraction, - local_dof_indices, - system_matrix_void_fraction, - system_rhs_void_fraction); - } - } - system_matrix_void_fraction.compress(VectorOperation::add); - system_rhs_void_fraction.compress(VectorOperation::add); -} - -template -void -FluidDynamicsVANS::quadrature_centered_sphere_method( - bool load_balance_step) -{ - QGauss quadrature_formula(this->number_quadrature_points); - const MappingQ mapping(1); - - FEValues fe_values_void_fraction(mapping, - this->fe_void_fraction, - quadrature_formula, - update_values | - update_quadrature_points | - update_JxW_values | update_gradients); - - const unsigned int dofs_per_cell = this->fe_void_fraction.dofs_per_cell; - std::vector local_dof_indices(dofs_per_cell); - const unsigned int n_q_points = quadrature_formula.size(); - FullMatrix local_matrix_void_fraction(dofs_per_cell, dofs_per_cell); - Vector local_rhs_void_fraction(dofs_per_cell); - std::vector phi_vf(dofs_per_cell); - std::vector> grad_phi_vf(dofs_per_cell); - - double r_sphere = 0.0; - double particles_volume_in_sphere; - double quadrature_void_fraction; - double qcm_sphere_diameter = - this->cfd_dem_simulation_parameters.void_fraction->qcm_sphere_diameter; - - // If the reference sphere diameter is user defined, the radius is calculated - // from it, otherwise, the value must be calculated while looping over the - // cells. - bool calculate_reference_sphere_radius = false; - if (qcm_sphere_diameter > 1e-16) - r_sphere = qcm_sphere_diameter / 2.0; - else - calculate_reference_sphere_radius = true; - - // Lambda functions for calculating the radius of the reference sphere - // Calculate the radius by the volume (area in 2D) of sphere: - // r = (2*dim*V/pi)^(1/dim) / 2 - auto radius_sphere_volume_cell = [](auto cell_measure) { - return 0.5 * pow(2.0 * dim * cell_measure / M_PI, 1.0 / dim); - }; - - // Calculate the radius is obtained from the volume of sphere based on - // R_s = h_omega: - // V_s = pi*(2*V_c^(1/dim))^(dim)/(2*dim) = pi*2^(dim)*V_c/(2*dim) - auto radius_h_omega = [&radius_sphere_volume_cell](double cell_measure) { - double reference_sphere_volume = - M_PI * Utilities::fixed_power(2.0) * cell_measure / (2.0 * dim); - - return radius_sphere_volume_cell(reference_sphere_volume); - }; - - system_rhs_void_fraction = 0; - system_matrix_void_fraction = 0; - - // Clear all contributions of particles from the previous timestep - for (const auto &cell : - this->void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - const auto pic = particle_handler.particles_in_cell(cell); - - for (auto &particle : pic) - { - auto particle_properties = particle.get_properties(); - - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] = 0; - } - } - } - - // Determine the new volumetric contributions of the particles necessary - // for void fraction calculation - for (const auto &cell : - this->void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - fe_values_void_fraction.reinit(cell); - - // Active neighbors include the current cell as well - auto active_neighbors = - LetheGridTools::find_cells_around_cell(vertices_to_cell, cell); - - auto active_periodic_neighbors = - LetheGridTools::find_cells_around_cell( - vertices_to_periodic_cell, cell); - - // Array of real locations for the quadrature points - std::vector>> - neighbor_quadrature_point_location( - active_neighbors.size(), std::vector>(n_q_points)); - - for (unsigned int n = 0; n < active_neighbors.size(); n++) - { - fe_values_void_fraction.reinit(active_neighbors[n]); - - neighbor_quadrature_point_location[n] = - fe_values_void_fraction.get_quadrature_points(); - } - - // Array of real locations for the periodic neighbor quadrature points - std::vector>> - periodic_neighbor_quadrature_point_location( - active_periodic_neighbors.size(), - std::vector>(n_q_points)); - - for (unsigned int n = 0; n < active_periodic_neighbors.size(); n++) - { - fe_values_void_fraction.reinit(active_periodic_neighbors[n]); - - periodic_neighbor_quadrature_point_location[n] = - fe_values_void_fraction.get_quadrature_points(); - } - - // Loop over the particles in the current cell - const auto pic = particle_handler.particles_in_cell(cell); - for (auto &particle : pic) - { - auto particle_properties = particle.get_properties(); - const double r_particle = - 0.5 * particle_properties[DEM::PropertiesIndex::dp]; - - // Loop over neighboring cells to determine if a given neighboring - // particle contributes to the solid volume of the current - // reference sphere - //*********************************************************************** - for (unsigned int n = 0; n < active_neighbors.size(); n++) - { - // Define the radius of the reference sphere to be used as the - // averaging volume for the QCM, if the reference sphere - // diameter was given by the user the value is already defined - // since it is not dependent on any measure of the active cell - if (calculate_reference_sphere_radius) - { - if (this->cfd_dem_simulation_parameters.void_fraction - ->qcm_sphere_equal_cell_volume == true) - { - // Get the radius by the volume of sphere which is - // equal to the volume of cell - r_sphere = radius_sphere_volume_cell( - active_neighbors[n]->measure()); - } - else - { - // The radius is obtained from the volume of sphere - // based on R_s = h_omega - r_sphere = - radius_h_omega(active_neighbors[n]->measure()); - } - } - - // Loop over quadrature points - for (unsigned int k = 0; k < n_q_points; ++k) - { - // Distance between particle and quadrature point - double neighbor_distance = - particle.get_location().distance( - neighbor_quadrature_point_location[n][k]); - - // Particle completely in reference sphere - if (neighbor_distance <= (r_sphere - r_particle)) - { - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] += - M_PI * - Utilities::fixed_power( - particle_properties[DEM::PropertiesIndex::dp]) / - (2.0 * dim); - } - - // Particle completely outside reference - // sphere. Do absolutely nothing. - - // Particle partially in reference sphere - else if ((neighbor_distance > (r_sphere - r_particle)) && - (neighbor_distance < (r_sphere + r_particle))) - { - if constexpr (dim == 2) - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] += - particle_circle_intersection_2d( - r_particle, r_sphere, neighbor_distance); - - else if constexpr (dim == 3) - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] += - particle_sphere_intersection_3d( - r_particle, r_sphere, neighbor_distance); - } - } - } - - // Loop over periodic neighboring cells to determine if a given - // neighboring particle contributes to the solid volume of the - // current reference sphere - //*********************************************************************** - for (unsigned int n = 0; n < active_periodic_neighbors.size(); - n++) - { - if (calculate_reference_sphere_radius) - { - if (this->cfd_dem_simulation_parameters.void_fraction - ->qcm_sphere_equal_cell_volume == true) - { - // Get the radius by the volume of sphere which is - // equal to the volume of cell - r_sphere = radius_sphere_volume_cell( - active_periodic_neighbors[n]->measure()); - } - else - { - // The radius is obtained from the volume of sphere - // based on R_s = h_omega - r_sphere = radius_h_omega( - active_periodic_neighbors[n]->measure()); - } - } - - // Loop over quadrature points - for (unsigned int k = 0; k < n_q_points; ++k) - { - // Adjust the location of the particle in the cell to - // account for the periodicity. If the position of the - // periodic cell if greater than the position of the - // current cell, the particle location needs a positive - // correction, and vice versa - const Point particle_location = - (active_periodic_neighbors[n] - ->center()[periodic_direction] > - cell->center()[periodic_direction]) ? - particle.get_location() + periodic_offset : - particle.get_location() - periodic_offset; - - // Distance between particle and quadrature point - double periodic_neighbor_distance = - particle_location.distance( - periodic_neighbor_quadrature_point_location[n][k]); - - // Particle completely in reference sphere - if (periodic_neighbor_distance <= (r_sphere - r_particle)) - { - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] += - M_PI * - Utilities::fixed_power( - particle_properties[DEM::PropertiesIndex::dp]) / - (2.0 * dim); - } - - // Particle completely outside reference - // sphere. Do absolutely nothing. - - // Particle partially in reference sphere - else if ((periodic_neighbor_distance > - (r_sphere - r_particle)) && - (periodic_neighbor_distance < - (r_sphere + r_particle))) - { - if constexpr (dim == 2) - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] += - particle_circle_intersection_2d( - r_particle, - r_sphere, - periodic_neighbor_distance); - - else if constexpr (dim == 3) - particle_properties - [DEM::PropertiesIndex::volumetric_contribution] += - particle_sphere_intersection_3d( - r_particle, - r_sphere, - periodic_neighbor_distance); - } - } - } - - //********************************************************************* - } - } - } - - // Update ghost particles - if (load_balance_step) - { - particle_handler.sort_particles_into_subdomains_and_cells(); - particle_handler.exchange_ghost_particles(true); - } - else - { - particle_handler.update_ghost_particles(); - } - - // After the particles' contributions have been determined, calculate and - // normalize the void fraction - for (const auto &cell : - this->void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - fe_values_void_fraction.reinit(cell); - - local_matrix_void_fraction = 0; - local_rhs_void_fraction = 0; - - double sum_quadrature_weights = 0; - - for (unsigned int q = 0; q < n_q_points; ++q) - { - sum_quadrature_weights += fe_values_void_fraction.JxW(q); - } - - // Define the volume of the reference sphere to be used as the - // averaging volume for the QCM - if (calculate_reference_sphere_radius) - { - if (this->cfd_dem_simulation_parameters.void_fraction - ->qcm_sphere_equal_cell_volume == true) - { - // Get the radius by the volume of sphere which is - // equal to the volume of cell - r_sphere = radius_sphere_volume_cell(cell->measure()); - } - else - { - // The radius is obtained from the volume of sphere based - // on R_s = h_omega - r_sphere = radius_h_omega(cell->measure()); - } - } - - // Array of real locations for the quadrature points - std::vector> quadrature_point_location; - - quadrature_point_location = - fe_values_void_fraction.get_quadrature_points(); - - // Active neighbors include the current cell as well - auto active_neighbors = - LetheGridTools::find_cells_around_cell(vertices_to_cell, cell); - - // Periodic neighbors of the current cell - auto active_periodic_neighbors = - LetheGridTools::find_cells_around_cell( - vertices_to_periodic_cell, cell); - - for (unsigned int q = 0; q < n_q_points; ++q) - { - particles_volume_in_sphere = 0; - quadrature_void_fraction = 0; - - for (unsigned int m = 0; m < active_neighbors.size(); m++) - { - // Loop over particles in neighbor cell - // Begin and end iterator for particles in neighbor cell - const auto pic = - particle_handler.particles_in_cell(active_neighbors[m]); - for (auto &particle : pic) - { - double distance = 0; - auto particle_properties = particle.get_properties(); - const double r_particle = - particle_properties[DEM::PropertiesIndex::dp] * 0.5; - double single_particle_volume = - M_PI * Utilities::fixed_power(r_particle * 2.0) / - (2 * dim); - - // Distance between particle and quadrature point - // centers - distance = particle.get_location().distance( - quadrature_point_location[q]); - - // Particle completely in reference sphere - if (distance <= (r_sphere - r_particle)) - particles_volume_in_sphere += - (M_PI * - Utilities::fixed_power( - particle_properties[DEM::PropertiesIndex::dp]) / - (2.0 * dim)) * - single_particle_volume / - particle_properties - [DEM::PropertiesIndex::volumetric_contribution]; - - // Particle completely outside reference sphere. Do - // absolutely nothing. - - // Particle partially in reference sphere - else if ((distance > (r_sphere - r_particle)) && - (distance < (r_sphere + r_particle))) - { - if (dim == 2) - particles_volume_in_sphere += - particle_circle_intersection_2d(r_particle, - r_sphere, - distance) * - single_particle_volume / - particle_properties - [DEM::PropertiesIndex::volumetric_contribution]; - else if (dim == 3) - particles_volume_in_sphere += - particle_sphere_intersection_3d(r_particle, - r_sphere, - distance) * - single_particle_volume / - particle_properties - [DEM::PropertiesIndex::volumetric_contribution]; - } - } - } - - // Execute same operations for periodic neighbors, if the - // simulation has no periodic boundaries, the container is empty. - // Also, those operations can not be done in the previous loop - // because the particles on the periodic side needs a correction - // with an offset for the distance with the quadrature point - for (unsigned int m = 0; m < active_periodic_neighbors.size(); - m++) - { - // Loop over particles in periodic neighbor cell - const auto pic = particle_handler.particles_in_cell( - active_periodic_neighbors[m]); - for (auto &particle : pic) - { - double distance = 0; - auto particle_properties = particle.get_properties(); - const double r_particle = - particle_properties[DEM::PropertiesIndex::dp] * 0.5; - double single_particle_volume = - M_PI * Utilities::fixed_power(r_particle * 2) / - (2 * dim); - - // Adjust the location of the particle in the cell to - // account for the periodicity. If the position of the - // periodic cell if greater than the position of the - // current cell, the particle location needs a negative - // correction, and vice versa. Since the particle is in - // the periodic cell, this correction is the inverse of - // the correction for the volumetric contribution - const Point particle_location = - (active_periodic_neighbors[m] - ->center()[periodic_direction] > - cell->center()[periodic_direction]) ? - particle.get_location() - periodic_offset : - particle.get_location() + periodic_offset; - - // Distance between particle and quadrature point - // centers - distance = particle_location.distance( - quadrature_point_location[q]); - - // Particle completely in reference sphere - if (distance <= (r_sphere - r_particle)) - particles_volume_in_sphere += - (M_PI * - Utilities::fixed_power( - particle_properties[DEM::PropertiesIndex::dp]) / - (2.0 * dim)) * - single_particle_volume / - particle_properties - [DEM::PropertiesIndex::volumetric_contribution]; - - // Particle completely outside reference sphere. Do - // absolutely nothing. - - // Particle partially in reference sphere - else if ((distance > (r_sphere - r_particle)) && - (distance < (r_sphere + r_particle))) - { - if (dim == 2) - particles_volume_in_sphere += - particle_circle_intersection_2d(r_particle, - r_sphere, - distance) * - single_particle_volume / - particle_properties - [DEM::PropertiesIndex::volumetric_contribution]; - else if (dim == 3) - particles_volume_in_sphere += - particle_sphere_intersection_3d(r_particle, - r_sphere, - distance) * - single_particle_volume / - particle_properties - [DEM::PropertiesIndex::volumetric_contribution]; - } - } - } - - // We use the volume of the cell as it is equal to the volume - // of the sphere - double cell_volume = compute_cell_measure_with_JxW( - fe_values_void_fraction.get_JxW_values()); - quadrature_void_fraction = - ((fe_values_void_fraction.JxW(q) * cell_volume / - sum_quadrature_weights) - - particles_volume_in_sphere) / - (fe_values_void_fraction.JxW(q) * cell_volume / - sum_quadrature_weights); - - for (unsigned int k = 0; k < dofs_per_cell; ++k) - { - phi_vf[k] = fe_values_void_fraction.shape_value(k, q); - grad_phi_vf[k] = fe_values_void_fraction.shape_grad(k, q); - } - - for (unsigned int i = 0; i < dofs_per_cell; ++i) - { - // Assemble L2 projection - // Matrix assembly - for (unsigned int j = 0; j < dofs_per_cell; ++j) - { - local_matrix_void_fraction(i, j) += - ((phi_vf[j] * phi_vf[i]) + - (this->cfd_dem_simulation_parameters.void_fraction - ->l2_smoothing_factor * - grad_phi_vf[j] * grad_phi_vf[i])) * - fe_values_void_fraction.JxW(q); - } - - local_rhs_void_fraction(i) += phi_vf[i] * - quadrature_void_fraction * - fe_values_void_fraction.JxW(q); - } - } - - cell->get_dof_indices(local_dof_indices); - void_fraction_constraints.distribute_local_to_global( - local_matrix_void_fraction, - local_rhs_void_fraction, - local_dof_indices, - system_matrix_void_fraction, - system_rhs_void_fraction); - } - } - - system_matrix_void_fraction.compress(VectorOperation::add); - system_rhs_void_fraction.compress(VectorOperation::add); -} - -template -void -FluidDynamicsVANS::satellite_point_method() -{ - QGauss quadrature_formula(this->number_quadrature_points); - const MappingQ mapping(1); - - FEValues fe_values_void_fraction(mapping, - this->fe_void_fraction, - quadrature_formula, - update_values | - update_quadrature_points | - update_JxW_values | update_gradients); - - const unsigned int dofs_per_cell = this->fe_void_fraction.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - FullMatrix local_matrix_void_fraction(dofs_per_cell, dofs_per_cell); - Vector local_rhs_void_fraction(dofs_per_cell); - std::vector local_dof_indices(dofs_per_cell); - std::vector phi_vf(dofs_per_cell); - std::vector> grad_phi_vf(dofs_per_cell); - - system_rhs_void_fraction = 0; - system_matrix_void_fraction = 0; - - // Creation of reference sphere and components required for mapping into - // individual particles - //------------------------------------------------------------------------- - QGauss quadrature_particle(1); - Triangulation particle_triangulation; - Point center; - - // Reference particle with radius 1 - GridGenerator::hyper_ball(particle_triangulation, center, 1); - particle_triangulation.refine_global( - this->cfd_dem_simulation_parameters.void_fraction - ->particle_refinement_factor); - - DoFHandler dof_handler_particle(particle_triangulation); - - FEValues fe_values_particle(mapping, - this->fe_void_fraction, - quadrature_particle, - update_JxW_values | - update_quadrature_points); - - dof_handler_particle.distribute_dofs(this->fe_void_fraction); - - std::vector> reference_quadrature_location( - quadrature_particle.size() * - particle_triangulation.n_global_active_cells()); - - std::vector reference_quadrature_weights( - quadrature_particle.size() * - particle_triangulation.n_global_active_cells()); - - std::vector> quadrature_particle_location( - quadrature_particle.size() * - particle_triangulation.n_global_active_cells()); - - std::vector quadrature_particle_weights( - quadrature_particle.size() * - particle_triangulation.n_global_active_cells()); - - unsigned int n = 0; - for (const auto &particle_cell : dof_handler_particle.active_cell_iterators()) - { - fe_values_particle.reinit(particle_cell); - for (unsigned int q = 0; q < quadrature_particle.size(); ++q) - { - reference_quadrature_weights[n] = - (M_PI * Utilities::fixed_power(2.0) / (2.0 * dim)) / - reference_quadrature_weights.size(); - reference_quadrature_location[n] = - fe_values_particle.quadrature_point(q); - n++; - } - } - //------------------------------------------------------------------------- - for (const auto &cell : - this->void_fraction_dof_handler.active_cell_iterators()) - { - if (cell->is_locally_owned()) - { - fe_values_void_fraction.reinit(cell); - - local_matrix_void_fraction = 0; - local_rhs_void_fraction = 0; - - double solid_volume_in_cell = 0; - - // Active neighbors include the current cell as well - auto active_neighbors = - LetheGridTools::find_cells_around_cell(vertices_to_cell, cell); - - for (unsigned int m = 0; m < active_neighbors.size(); m++) - { - // Loop over particles in cell - // Begin and end iterator for particles in cell - const auto pic = - particle_handler.particles_in_cell(active_neighbors[m]); - for (auto &particle : pic) - { - /*****************************************************************/ - auto particle_properties = particle.get_properties(); - auto particle_location = particle.get_location(); - - // Translation factor used to translate the reference sphere - // location and size to those of the particles. Usually, we - // take it as the radius of every individual particle. This - // makes our method valid for different particle distribution. - double translational_factor = - particle_properties[DEM::PropertiesIndex::dp] * 0.5; - - // Resize and translate reference sphere - // to the particle size and position according the volume - // ratio between sphere and particle. - for (unsigned int l = 0; - l < reference_quadrature_location.size(); - ++l) - { - // For example, in 3D V_particle/V_sphere = - // r_particle³/r_sphere³ and since r_sphere is always 1, - // then V_particle/V_sphere = r_particle³. Therefore, - // V_particle = r_particle³ * V_sphere. - quadrature_particle_weights[l] = - Utilities::fixed_power(translational_factor) * - reference_quadrature_weights[l]; - - // Here, we translate the position of the reference sphere - // into the position of the particle, but for this we have - // to shrink or expand the size of the reference sphere to - // be equal to the size of the particle as the location of - // the quadrature points is affected by the size by - // multiplying with the particle's radius. We then - // translate by taking the translational vector between - // the reference sphere center and the particle's center. - // This translates directly into the translational vector - // being the particle's position as the reference sphere - // is always located at (0,0) in 2D or (0,0,0) in 3D. - quadrature_particle_location[l] = - (translational_factor * - reference_quadrature_location[l]) + - particle_location; - - if (cell->point_inside(quadrature_particle_location[l])) - solid_volume_in_cell += quadrature_particle_weights[l]; - } - } - } - - // Same steps for the periodic neighbors with particle location - // correction - auto active_periodic_neighbors = - LetheGridTools::find_cells_around_cell( - vertices_to_periodic_cell, cell); - - for (unsigned int m = 0; m < active_periodic_neighbors.size(); m++) - { - const auto pic = particle_handler.particles_in_cell( - active_periodic_neighbors[m]); - for (auto &particle : pic) - { - /*****************************************************************/ - auto particle_properties = particle.get_properties(); - const Point particle_location = - (active_periodic_neighbors[m] - ->center()[periodic_direction] > - cell->center()[periodic_direction]) ? - particle.get_location() - periodic_offset : - particle.get_location() + periodic_offset; - - double translational_factor = - particle_properties[DEM::PropertiesIndex::dp] * 0.5; - - for (unsigned int l = 0; - l < reference_quadrature_location.size(); - ++l) - { - quadrature_particle_weights[l] = - Utilities::fixed_power(translational_factor) * - reference_quadrature_weights[l]; - - quadrature_particle_location[l] = - (translational_factor * - reference_quadrature_location[l]) + - particle_location; - - if (cell->point_inside(quadrature_particle_location[l])) - solid_volume_in_cell += quadrature_particle_weights[l]; - } - } - } - - double cell_volume = compute_cell_measure_with_JxW( - fe_values_void_fraction.get_JxW_values()); - - // Calculate cell void fraction - double cell_void_fraction = - (cell_volume - solid_volume_in_cell) / cell_volume; - - for (unsigned int q = 0; q < n_q_points; ++q) - { - for (unsigned int k = 0; k < dofs_per_cell; ++k) - { - phi_vf[k] = fe_values_void_fraction.shape_value(k, q); - grad_phi_vf[k] = fe_values_void_fraction.shape_grad(k, q); - } - for (unsigned int i = 0; i < dofs_per_cell; ++i) - { - // Matrix assembly - for (unsigned int j = 0; j < dofs_per_cell; ++j) - { - local_matrix_void_fraction(i, j) += - (phi_vf[j] * phi_vf[i]) * - fe_values_void_fraction.JxW(q) + - (this->cfd_dem_simulation_parameters.void_fraction - ->l2_smoothing_factor * - grad_phi_vf[j] * grad_phi_vf[i] * - fe_values_void_fraction.JxW(q)); - } - local_rhs_void_fraction(i) += phi_vf[i] * cell_void_fraction * - fe_values_void_fraction.JxW(q); - } - } - cell->get_dof_indices(local_dof_indices); - void_fraction_constraints.distribute_local_to_global( - local_matrix_void_fraction, - local_rhs_void_fraction, - local_dof_indices, - system_matrix_void_fraction, - system_rhs_void_fraction); - } - } - - system_matrix_void_fraction.compress(VectorOperation::add); - system_rhs_void_fraction.compress(VectorOperation::add); -} - - -template -void -FluidDynamicsVANS::solve_L2_system_void_fraction() -{ - // Solve the L2 projection system - const double linear_solver_tolerance = 1e-15; - - if (this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver - .at(PhysicsID::fluid_dynamics) - .verbosity != Parameters::Verbosity::quiet) - { - this->pcout << " -Tolerance of iterative solver is : " - << linear_solver_tolerance << std::endl; - } - - const IndexSet locally_owned_dofs_voidfraction = - void_fraction_dof_handler.locally_owned_dofs(); - - GlobalVectorType completely_distributed_solution( - locally_owned_dofs_voidfraction, this->mpi_communicator); - - SolverControl solver_control( - this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver - .at(PhysicsID::fluid_dynamics) - .max_iterations, - linear_solver_tolerance, - true, - true); - - TrilinosWrappers::SolverCG solver(solver_control); - - //********************************************** - // Trillinos Wrapper ILU Preconditioner - //********************************************* - const double ilu_fill = - this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver - .at(PhysicsID::fluid_dynamics) - .ilu_precond_fill; - const double ilu_atol = - this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver - .at(PhysicsID::fluid_dynamics) - .ilu_precond_atol; - const double ilu_rtol = - this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver - .at(PhysicsID::fluid_dynamics) - .ilu_precond_rtol; - - TrilinosWrappers::PreconditionILU::AdditionalData preconditionerOptions( - ilu_fill, ilu_atol, ilu_rtol, 0); - - ilu_preconditioner = std::make_shared(); - - ilu_preconditioner->initialize(system_matrix_void_fraction, - preconditionerOptions); - - solver.solve(system_matrix_void_fraction, - completely_distributed_solution, - system_rhs_void_fraction, - *ilu_preconditioner); - - if (this->cfd_dem_simulation_parameters.cfd_parameters.linear_solver - .at(PhysicsID::fluid_dynamics) - .verbosity != Parameters::Verbosity::quiet) - { - this->pcout << " -Iterative solver took : " << solver_control.last_step() - << " steps " << std::endl; - } - - void_fraction_constraints.distribute(completely_distributed_solution); - nodal_void_fraction_relevant = completely_distributed_solution; + this->void_fraction_manager.dof_handler, vertices_to_periodic_cell); } // Do an iteration with the NavierStokes Solver @@ -1602,7 +371,7 @@ FluidDynamicsVANS::assemble_system_matrix() *this->mapping, *this->face_quadrature); - scratch_data.enable_void_fraction(fe_void_fraction, + scratch_data.enable_void_fraction(*void_fraction_manager.fe, *this->cell_quadrature, *this->mapping); @@ -1646,19 +415,22 @@ FluidDynamicsVANS::assemble_local_system_matrix( &(*(this->triangulation)), cell->level(), cell->index(), - &this->void_fraction_dof_handler); - - scratch_data.reinit_void_fraction(void_fraction_cell, - nodal_void_fraction_relevant, - previous_void_fraction); - - scratch_data.reinit_particle_fluid_interactions(cell, - this->evaluation_point, - this->previous_solutions[0], - nodal_void_fraction_relevant, - particle_handler, - this->dof_handler, - void_fraction_dof_handler); + &this->void_fraction_manager.dof_handler); + + scratch_data.reinit_void_fraction( + void_fraction_cell, + void_fraction_manager.void_fraction_locally_relevant, + void_fraction_manager.previous_void_fraction); + + scratch_data.reinit_particle_fluid_interactions( + cell, + this->evaluation_point, + this->previous_solutions[0], + this->void_fraction_manager.void_fraction_locally_relevant, + particle_handler, + this->dof_handler, + void_fraction_manager.dof_handler); + scratch_data.calculate_physical_properties(); copy_data.reset(); @@ -1707,7 +479,7 @@ FluidDynamicsVANS::assemble_system_rhs() *this->mapping, *this->face_quadrature); - scratch_data.enable_void_fraction(fe_void_fraction, + scratch_data.enable_void_fraction(*void_fraction_manager.fe, *this->cell_quadrature, *this->mapping); @@ -1754,19 +526,21 @@ FluidDynamicsVANS::assemble_local_system_rhs( &(*(this->triangulation)), cell->level(), cell->index(), - &this->void_fraction_dof_handler); + &this->void_fraction_manager.dof_handler); - scratch_data.reinit_void_fraction(void_fraction_cell, - nodal_void_fraction_relevant, - previous_void_fraction); + scratch_data.reinit_void_fraction( + void_fraction_cell, + void_fraction_manager.void_fraction_locally_relevant, + void_fraction_manager.previous_void_fraction); - scratch_data.reinit_particle_fluid_interactions(cell, - this->evaluation_point, - this->previous_solutions[0], - nodal_void_fraction_relevant, - particle_handler, - this->dof_handler, - void_fraction_dof_handler); + scratch_data.reinit_particle_fluid_interactions( + cell, + this->evaluation_point, + this->previous_solutions[0], + void_fraction_manager.void_fraction_locally_relevant, + particle_handler, + this->dof_handler, + void_fraction_manager.dof_handler); scratch_data.calculate_physical_properties(); copy_data.reset(); @@ -1802,8 +576,8 @@ template void FluidDynamicsVANS::output_field_hook(DataOut &data_out) { - data_out.add_data_vector(void_fraction_dof_handler, - nodal_void_fraction_relevant, + data_out.add_data_vector(void_fraction_manager.dof_handler, + void_fraction_manager.void_fraction_locally_relevant, "void_fraction"); } @@ -1821,7 +595,7 @@ FluidDynamicsVANS::monitor_mass_conservation() update_hessians); FEValues fe_values_void_fraction(*this->mapping, - this->fe_void_fraction, + *this->void_fraction_manager.fe, quadrature_formula, update_values | update_quadrature_points | @@ -1865,14 +639,16 @@ FluidDynamicsVANS::monitor_mass_conservation() &(*this->triangulation), cell->level(), cell->index(), - &this->void_fraction_dof_handler); + &this->void_fraction_manager.dof_handler); fe_values_void_fraction.reinit(void_fraction_cell); // Gather void fraction (values, gradient) fe_values_void_fraction.get_function_values( - nodal_void_fraction_relevant, present_void_fraction_values); + void_fraction_manager.void_fraction_locally_relevant, + present_void_fraction_values); fe_values_void_fraction.get_function_gradients( - nodal_void_fraction_relevant, present_void_fraction_gradients); + void_fraction_manager.void_fraction_locally_relevant, + present_void_fraction_gradients); fe_values.reinit(cell); @@ -1897,17 +673,20 @@ FluidDynamicsVANS::monitor_mass_conservation() Parameters::SimulationControl::TimeSteppingMethod::steady) { fe_values_void_fraction.get_function_values( - previous_void_fraction[0], p1_void_fraction_values); + void_fraction_manager.previous_void_fraction[0], + p1_void_fraction_values); if (scheme == Parameters::SimulationControl::TimeSteppingMethod::bdf2) fe_values_void_fraction.get_function_values( - previous_void_fraction[1], p2_void_fraction_values); + void_fraction_manager.previous_void_fraction[1], + p2_void_fraction_values); if (scheme == Parameters::SimulationControl::TimeSteppingMethod::bdf3) fe_values_void_fraction.get_function_values( - previous_void_fraction[2], p3_void_fraction_values); + void_fraction_manager.previous_void_fraction[2], + p3_void_fraction_values); } local_mass_source = 0; @@ -2021,15 +800,15 @@ FluidDynamicsVANS::solve() if (this->simulation_control->is_at_start()) { vertices_cell_mapping(); - initialize_void_fraction(); + void_fraction_manager.initialize_void_fraction( + this->simulation_control->get_current_time()); this->iterate(); } else { NavierStokesBase::refine_mesh(); vertices_cell_mapping(); - calculate_void_fraction(this->simulation_control->get_current_time(), - false); + calculate_void_fraction(this->simulation_control->get_current_time()); this->iterate(); } diff --git a/source/fem-dem/parameters_cfd_dem.cc b/source/fem-dem/parameters_cfd_dem.cc index 1ac6732b20..3af6cd403f 100644 --- a/source/fem-dem/parameters_cfd_dem.cc +++ b/source/fem-dem/parameters_cfd_dem.cc @@ -7,7 +7,7 @@ namespace Parameters { template void - VoidFraction::declare_parameters(ParameterHandler &prm) + VoidFractionParameters::declare_parameters(ParameterHandler &prm) { prm.enter_subsection("void fraction"); prm.declare_entry( @@ -22,11 +22,6 @@ namespace Parameters "false", Patterns::Bool(), "Define particles using a DEM simulation results file."); - prm.declare_entry( - "bound void fraction", - "false", - Patterns::Bool(), - "Boolean for the bounding of the void fraction using an active set method."); prm.declare_entry("dem file name", "dem", Patterns::FileName(), @@ -35,14 +30,6 @@ namespace Parameters "0.000001", Patterns::Double(), "The smoothing factor for void fraction L2 projection"); - prm.declare_entry("l2 lower bound", - "0.36", - Patterns::Double(), - "The lower bound for void fraction L2 projection"); - prm.declare_entry("l2 upper bound", - "1", - Patterns::Double(), - "The upper bound for void fraction L2 projection"); prm.declare_entry( "particle refinement factor", "0", @@ -63,7 +50,7 @@ namespace Parameters template void - VoidFraction::parse_parameters(ParameterHandler &prm) + VoidFractionParameters::parse_parameters(ParameterHandler &prm) { prm.enter_subsection("void fraction"); const std::string op = prm.get("mode"); @@ -82,11 +69,8 @@ namespace Parameters prm.leave_subsection(); read_dem = prm.get_bool("read dem"); - bound_void_fraction = prm.get_bool("bound void fraction"); dem_file_name = prm.get("dem file name"); l2_smoothing_factor = prm.get_double("l2 smoothing factor"); - l2_lower_bound = prm.get_double("l2 lower bound"); - l2_upper_bound = prm.get_double("l2 upper bound"); particle_refinement_factor = prm.get_integer("particle refinement factor"); qcm_sphere_diameter = prm.get_double("qcm sphere diameter"); qcm_sphere_equal_cell_volume = prm.get_bool("qcm sphere equal cell volume"); @@ -228,5 +212,5 @@ namespace Parameters } } // namespace Parameters // Pre-compile the 2D and 3D -template class Parameters::VoidFraction<2>; -template class Parameters::VoidFraction<3>; +template class Parameters::VoidFractionParameters<2>; +template class Parameters::VoidFractionParameters<3>; diff --git a/source/fem-dem/void_fraction.cc b/source/fem-dem/void_fraction.cc new file mode 100644 index 0000000000..f8a34c31cc --- /dev/null +++ b/source/fem-dem/void_fraction.cc @@ -0,0 +1,1140 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020-2024 The Lethe Authors +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later + +#include + +#include + +#include + +#include + +#include + +#include +#include +#include + +#include + + +using namespace dealii; + +template +void +VoidFractionBase::setup_dofs() +{ + // Get a constant copy of the communicator since it is used extensively to + // establish the void fraction vectors + const MPI_Comm mpi_communicator = dof_handler.get_communicator(); + + dof_handler.distribute_dofs(*fe); + locally_owned_dofs = dof_handler.locally_owned_dofs(); + + locally_relevant_dofs = DoFTools::extract_locally_relevant_dofs(dof_handler); + + void_fraction_constraints.clear(); + void_fraction_constraints.reinit(locally_relevant_dofs); + DoFTools::make_hanging_node_constraints(dof_handler, + void_fraction_constraints); + + void_fraction_constraints.close(); + + void_fraction_locally_relevant.reinit(locally_owned_dofs, + locally_relevant_dofs, + mpi_communicator); + + this->previous_void_fraction.resize(maximum_number_of_previous_solutions()); + + // Initialize vector of previous solutions for the void fraction + for (auto &solution : this->previous_void_fraction) + { + solution.reinit(this->locally_owned_dofs, + this->locally_relevant_dofs, + this->triangulation->get_communicator()); + } + + void_fraction_locally_owned.reinit(locally_owned_dofs, + this->triangulation->get_communicator()); + + + DynamicSparsityPattern dsp(locally_relevant_dofs); + DoFTools::make_sparsity_pattern(dof_handler, + dsp, + void_fraction_constraints, + false); + + SparsityTools::distribute_sparsity_pattern( + dsp, + locally_owned_dofs, + this->triangulation->get_communicator(), + locally_relevant_dofs); + + system_matrix_void_fraction.reinit(locally_owned_dofs, + locally_owned_dofs, + dsp, + this->triangulation->get_communicator()); + + + system_rhs_void_fraction.reinit(locally_owned_dofs, + this->triangulation->get_communicator()); + + + // Vertices to cell mapping + LetheGridTools::vertices_cell_mapping(this->dof_handler, vertices_to_cell); +} + + +template +void +VoidFractionBase::setup_constraints( + const BoundaryConditions::NSBoundaryConditions &boundary_conditions) +{ + has_periodic_boundaries = false; + // Define constraints for periodic boundary conditions + void_fraction_constraints.clear(); + void_fraction_constraints.reinit(locally_relevant_dofs); + DoFTools::make_hanging_node_constraints(dof_handler, + void_fraction_constraints); + + + for (auto const &[id, type] : boundary_conditions.type) + { + if (type == BoundaryConditions::BoundaryType::periodic) + { + periodic_direction = boundary_conditions.periodic_direction.at(id); + DoFTools::make_periodicity_constraints( + this->dof_handler, + id, + boundary_conditions.periodic_neighbor_id.at(id), + periodic_direction, + this->void_fraction_constraints); + + has_periodic_boundaries = true; + + // Get periodic offset if void fraction method is qcm or spm + if (this->void_fraction_parameters->mode == + Parameters::VoidFractionMode::qcm || + this->void_fraction_parameters->mode == + Parameters::VoidFractionMode::spm) + { + periodic_offset = get_periodic_offset_distance(id); + } + } + } + void_fraction_constraints.close(); + + + // Reinit system matrix + + DynamicSparsityPattern dsp(locally_relevant_dofs); + DoFTools::make_sparsity_pattern(dof_handler, + dsp, + void_fraction_constraints, + false); + + SparsityTools::distribute_sparsity_pattern( + dsp, + locally_owned_dofs, + this->triangulation->get_communicator(), + locally_relevant_dofs); + + system_matrix_void_fraction.reinit(locally_owned_dofs, + locally_owned_dofs, + dsp, + this->triangulation->get_communicator()); + + if (has_periodic_boundaries) + LetheGridTools::vertices_cell_mapping_with_periodic_boundaries( + this->dof_handler, this->vertices_to_periodic_cell); +} + + +template +void +VoidFractionBase::calculate_void_fraction(const double time) +{ + announce_string(this->pcout, "Void Fraction"); + + if (void_fraction_parameters->mode == Parameters::VoidFractionMode::function) + { + calculate_void_fraction_function(time); + // If its a function, no need to solve a linear system of equations so + // return. + return; + } + // The void fraction is established using a particle handler. + // A right-hand side and a linear system of equation are assembled and then + // solved. The resulting solution yields the nodal values of the void + // fraction. + if (void_fraction_parameters->mode == Parameters::VoidFractionMode::pcm) + { + calculate_void_fraction_particle_centered_method(); + } + else if (void_fraction_parameters->mode == Parameters::VoidFractionMode::qcm) + { + calculate_void_fraction_quadrature_centered_method(); + } + else if (void_fraction_parameters->mode == Parameters::VoidFractionMode::spm) + { + calculate_void_fraction_satellite_point_method(); + } + + solve_linear_system_and_update_solution(false); +} + +template +void +VoidFractionBase::calculate_void_fraction_function(const double time) +{ + // The current time of the function is set for time-dependant functions + void_fraction_parameters->void_fraction.set_time(time); + + // The function is directly interpolate at the nodes. + // This is not an L2 projection, but a direct evaluation. + // This may lead to some issues on coarses meshes if a high-order + // interpolation (>FE_Q(1)) is used. + VectorTools::interpolate(*mapping, + dof_handler, + void_fraction_parameters->void_fraction, + void_fraction_locally_owned); + + // Propagate ghost values + void_fraction_locally_relevant = void_fraction_locally_owned; +} + +template +void +VoidFractionBase::calculate_void_fraction_particle_centered_method() +{ + const double l2_smoothing_factor = + void_fraction_parameters->l2_smoothing_factor; + + FEValues fe_values_void_fraction(*mapping, + *fe, + *quadrature, + update_values | update_JxW_values | + update_gradients); + + const unsigned int dofs_per_cell = fe->dofs_per_cell; + const unsigned int n_q_points = quadrature->size(); + FullMatrix local_matrix_void_fraction(dofs_per_cell, dofs_per_cell); + Vector local_rhs_void_fraction(dofs_per_cell); + std::vector local_dof_indices(dofs_per_cell); + std::vector phi_vf(dofs_per_cell); + std::vector> grad_phi_vf(dofs_per_cell); + + system_rhs_void_fraction = 0; + system_matrix_void_fraction = 0; + + for (const auto &cell : this->dof_handler.active_cell_iterators()) + { + if (cell->is_locally_owned()) + { + fe_values_void_fraction.reinit(cell); + + local_matrix_void_fraction = 0; + local_rhs_void_fraction = 0; + + double solid_volume_in_cell = 0; + + // Loop over particles in cell + // Begin and end iterator for particles in cell + const auto pic = particle_handler->particles_in_cell(cell); + for (auto &particle : pic) + { + auto particle_properties = particle.get_properties(); + if constexpr (dim == 2) + { + solid_volume_in_cell += + M_PI * 0.25 * + Utilities::fixed_power<2>( + particle_properties[DEM::PropertiesIndex::dp]); + } + if constexpr (dim == 3) + { + solid_volume_in_cell += + M_PI * 1. / 6. * + Utilities::fixed_power<3>( + particle_properties[DEM::PropertiesIndex::dp]); + } + } + double cell_volume = cell->measure(); + + // Calculate cell void fraction + double cell_void_fraction = + (cell_volume - solid_volume_in_cell) / cell_volume; + + for (unsigned int q = 0; q < n_q_points; ++q) + { + const double JxW = fe_values_void_fraction.JxW(q); + + for (unsigned int k = 0; k < dofs_per_cell; ++k) + { + phi_vf[k] = fe_values_void_fraction.shape_value(k, q); + grad_phi_vf[k] = fe_values_void_fraction.shape_grad(k, q); + } + for (unsigned int i = 0; i < dofs_per_cell; ++i) + { + // Assemble L2 projection + // Matrix assembly + for (unsigned int j = 0; j < dofs_per_cell; ++j) + { + local_matrix_void_fraction(i, j) += + (phi_vf[j] * phi_vf[i] + l2_smoothing_factor * + grad_phi_vf[j] * + grad_phi_vf[i]) * + JxW; + } + local_rhs_void_fraction(i) += + phi_vf[i] * cell_void_fraction * JxW; + } + } + cell->get_dof_indices(local_dof_indices); + void_fraction_constraints.distribute_local_to_global( + local_matrix_void_fraction, + local_rhs_void_fraction, + local_dof_indices, + system_matrix_void_fraction, + system_rhs_void_fraction); + } + } + system_matrix_void_fraction.compress(VectorOperation::add); + system_rhs_void_fraction.compress(VectorOperation::add); +} + +template +void +VoidFractionBase::calculate_void_fraction_satellite_point_method() +{ + FEValues fe_values_void_fraction(*mapping, + *fe, + *quadrature, + update_values | update_JxW_values | + update_gradients); + + const unsigned int dofs_per_cell = fe->dofs_per_cell; + const unsigned int n_q_points = quadrature->size(); + FullMatrix local_matrix_void_fraction(dofs_per_cell, dofs_per_cell); + Vector local_rhs_void_fraction(dofs_per_cell); + std::vector local_dof_indices(dofs_per_cell); + std::vector phi_vf(dofs_per_cell); + std::vector> grad_phi_vf(dofs_per_cell); + + system_rhs_void_fraction = 0; + system_matrix_void_fraction = 0; + + // Creation of reference sphere and components required for mapping into + // individual particles. This calculation is done once and cached + // since it requires creating a triangulation and quadrature points which + // would prohibitively expensive to do for each individual particle. + //------------------------------------------------------------------------- + QGauss quadrature_particle(1); + Triangulation particle_triangulation; + Point center; + + // Reference particle with radius 1 + GridGenerator::hyper_ball(particle_triangulation, center, 1); + particle_triangulation.refine_global( + void_fraction_parameters->particle_refinement_factor); + + DoFHandler dof_handler_particle(particle_triangulation); + + FEValues fe_values_particle(*mapping, + *fe, + quadrature_particle, + update_JxW_values | + update_quadrature_points); + + dof_handler_particle.distribute_dofs(*fe); + + std::vector> reference_quadrature_location( + quadrature_particle.size() * + particle_triangulation.n_global_active_cells()); + + std::vector reference_quadrature_weights( + quadrature_particle.size() * + particle_triangulation.n_global_active_cells()); + + std::vector> quadrature_particle_location( + quadrature_particle.size() * + particle_triangulation.n_global_active_cells()); + + std::vector quadrature_particle_weights( + quadrature_particle.size() * + particle_triangulation.n_global_active_cells()); + + unsigned int n = 0; + for (const auto &particle_cell : dof_handler_particle.active_cell_iterators()) + { + fe_values_particle.reinit(particle_cell); + for (unsigned int q = 0; q < quadrature_particle.size(); ++q) + { + reference_quadrature_weights[n] = + (M_PI * Utilities::fixed_power(2.0) / (2.0 * dim)) / + reference_quadrature_weights.size(); + reference_quadrature_location[n] = + fe_values_particle.quadrature_point(q); + n++; + } + } + //------------------------------------------------------------------------- + // At this stage, everything related to the reference particle has been + // correctly pre-calculated and stored. Now we assemble the system using + // the satellite point method to calculate the void fraction adequately. + + for (const auto &cell : this->dof_handler.active_cell_iterators()) + { + if (cell->is_locally_owned()) + { + fe_values_void_fraction.reinit(cell); + + local_matrix_void_fraction = 0; + local_rhs_void_fraction = 0; + + double solid_volume_in_cell = 0; + + // Active neighbors include the current cell as well + auto active_neighbors = + LetheGridTools::find_cells_around_cell(vertices_to_cell, cell); + + for (unsigned int m = 0; m < active_neighbors.size(); m++) + { + // Loop over particles in cell + // Begin and end iterator for particles in cell + const auto pic = + particle_handler->particles_in_cell(active_neighbors[m]); + for (auto &particle : pic) + { + auto particle_properties = particle.get_properties(); + auto particle_location = particle.get_location(); + + // Translation factor used to translate the reference sphere + // location and size to those of the particles. Usually, we + // take it as the radius of every individual particle. This + // makes our method valid for different particle + // distribution. + double translational_factor = + particle_properties[DEM::PropertiesIndex::dp] * 0.5; + + // Resize and translate the reference sphere + // to the particle size and position according the volume + // ratio between sphere and particle. + for (unsigned int l = 0; + l < reference_quadrature_location.size(); + ++l) + { + // For example, in 3D V_particle/V_sphere = + // r_particle³/r_sphere³ and since r_sphere is always 1, + // then V_particle/V_sphere = r_particle³. Therefore, + // V_particle = r_particle³ * V_sphere. + quadrature_particle_weights[l] = + Utilities::fixed_power(translational_factor) * + reference_quadrature_weights[l]; + + // Here, we translate the position of the reference + // sphere into the position of the particle, but for + // this we have to shrink or expand the size of the + // reference sphere to be equal to the size of the + // particle as the location of the quadrature points is + // affected by the size by multiplying with the + // particle's radius. We then translate by taking the + // translational vector between the reference sphere + // center and the particle's center. This translates + // directly into the translational vector being the + // particle's position as the reference sphere is always + // located at (0,0) in 2D or (0,0,0) in 3D. + quadrature_particle_location[l] = + (translational_factor * + reference_quadrature_location[l]) + + particle_location; + + if (cell->point_inside(quadrature_particle_location[l])) + solid_volume_in_cell += quadrature_particle_weights[l]; + } + } + } + + // Same steps for the periodic neighbors with particle location + // correction + auto active_periodic_neighbors = + LetheGridTools::find_cells_around_cell( + vertices_to_periodic_cell, cell); + + for (unsigned int m = 0; m < active_periodic_neighbors.size(); m++) + { + const auto pic = particle_handler->particles_in_cell( + active_periodic_neighbors[m]); + for (auto &particle : pic) + { + auto particle_properties = particle.get_properties(); + const Point particle_location = + (active_periodic_neighbors[m] + ->center()[periodic_direction] > + cell->center()[periodic_direction]) ? + particle.get_location() - periodic_offset : + particle.get_location() + periodic_offset; + + double translational_factor = + particle_properties[DEM::PropertiesIndex::dp] * 0.5; + + for (unsigned int l = 0; + l < reference_quadrature_location.size(); + ++l) + { + quadrature_particle_weights[l] = + Utilities::fixed_power(translational_factor) * + reference_quadrature_weights[l]; + + quadrature_particle_location[l] = + (translational_factor * + reference_quadrature_location[l]) + + particle_location; + + if (cell->point_inside(quadrature_particle_location[l])) + solid_volume_in_cell += quadrature_particle_weights[l]; + } + } + } + + double cell_volume = cell->measure(); + + // Calculate cell void fraction + double cell_void_fraction = + (cell_volume - solid_volume_in_cell) / cell_volume; + + for (unsigned int q = 0; q < n_q_points; ++q) + { + for (unsigned int k = 0; k < dofs_per_cell; ++k) + { + phi_vf[k] = fe_values_void_fraction.shape_value(k, q); + grad_phi_vf[k] = fe_values_void_fraction.shape_grad(k, q); + } + for (unsigned int i = 0; i < dofs_per_cell; ++i) + { + // Matrix assembly + for (unsigned int j = 0; j < dofs_per_cell; ++j) + { + local_matrix_void_fraction(i, j) += + (phi_vf[j] * phi_vf[i]) * + fe_values_void_fraction.JxW(q) + + (void_fraction_parameters->l2_smoothing_factor * + grad_phi_vf[j] * grad_phi_vf[i] * + fe_values_void_fraction.JxW(q)); + } + local_rhs_void_fraction(i) += phi_vf[i] * cell_void_fraction * + fe_values_void_fraction.JxW(q); + } + } + cell->get_dof_indices(local_dof_indices); + void_fraction_constraints.distribute_local_to_global( + local_matrix_void_fraction, + local_rhs_void_fraction, + local_dof_indices, + system_matrix_void_fraction, + system_rhs_void_fraction); + } + } + + system_matrix_void_fraction.compress(VectorOperation::add); + system_rhs_void_fraction.compress(VectorOperation::add); +} + +template +void +VoidFractionBase::calculate_void_fraction_quadrature_centered_method() +{ + FEValues fe_values_void_fraction(*mapping, + *fe, + *quadrature, + update_values | + update_quadrature_points | + update_JxW_values | update_gradients); + + const unsigned int dofs_per_cell = this->fe->dofs_per_cell; + std::vector local_dof_indices(dofs_per_cell); + const unsigned int n_q_points = quadrature->size(); + FullMatrix local_matrix_void_fraction(dofs_per_cell, dofs_per_cell); + Vector local_rhs_void_fraction(dofs_per_cell); + std::vector phi_vf(dofs_per_cell); + std::vector> grad_phi_vf(dofs_per_cell); + + double r_sphere = 0.0; + double particles_volume_in_sphere; + double quadrature_void_fraction; + double qcm_sphere_diameter = void_fraction_parameters->qcm_sphere_diameter; + + // If the reference sphere diameter is user-defined, the radius is + // calculated from it, otherwise, the value must be calculated while looping + // over the cells. + bool calculate_reference_sphere_radius = true; + if (qcm_sphere_diameter > 1e-16) + { + r_sphere = 0.5 * qcm_sphere_diameter; + calculate_reference_sphere_radius = false; + } + + // Lambda functions for calculating the radius of the reference sphere + // Calculate the radius by the volume (area in 2D) of sphere: + // r = (2*dim*V/pi)^(1/dim) / 2 + auto radius_sphere_volume_cell = [](auto cell_measure) { + return 0.5 * pow(2.0 * dim * cell_measure / M_PI, 1.0 / double(dim)); + }; + + // Calculate the radius is obtained from the volume of sphere based on + // R_s = h_omega: + // V_s = pi*(2*V_c^(1/dim))^(dim)/(2*dim) = pi*2^(dim)*V_c/(2*dim) + auto radius_h_omega = [&radius_sphere_volume_cell](double cell_measure) { + double reference_sphere_volume = + M_PI * Utilities::fixed_power(2.0) * cell_measure / (2.0 * dim); + + return radius_sphere_volume_cell(reference_sphere_volume); + }; + + system_rhs_void_fraction = 0; + system_matrix_void_fraction = 0; + + // Clear all contributions of particles from the previous time-step + for (const auto &cell : dof_handler.active_cell_iterators()) + { + if (cell->is_locally_owned()) + { + const auto pic = particle_handler->particles_in_cell(cell); + + for (auto &particle : pic) + { + auto particle_properties = particle.get_properties(); + + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] = 0; + } + } + } + + // Determine the new volumetric contributions of the particles necessary + // for void fraction calculation + for (const auto &cell : dof_handler.active_cell_iterators()) + { + if (cell->is_locally_owned()) + { + fe_values_void_fraction.reinit(cell); + + // Active neighbors include the current cell as well + auto active_neighbors = + LetheGridTools::find_cells_around_cell(vertices_to_cell, cell); + + auto active_periodic_neighbors = + LetheGridTools::find_cells_around_cell( + vertices_to_periodic_cell, cell); + + // Array of real locations for the quadrature points + std::vector>> + neighbor_quadrature_point_location( + active_neighbors.size(), std::vector>(n_q_points)); + + for (unsigned int n = 0; n < active_neighbors.size(); n++) + { + fe_values_void_fraction.reinit(active_neighbors[n]); + + neighbor_quadrature_point_location[n] = + fe_values_void_fraction.get_quadrature_points(); + } + + // Array of real locations for the periodic neighbor quadrature + // points + std::vector>> + periodic_neighbor_quadrature_point_location( + active_periodic_neighbors.size(), + std::vector>(n_q_points)); + + for (unsigned int n = 0; n < active_periodic_neighbors.size(); n++) + { + fe_values_void_fraction.reinit(active_periodic_neighbors[n]); + + periodic_neighbor_quadrature_point_location[n] = + fe_values_void_fraction.get_quadrature_points(); + } + + // Loop over the particles in the current cell + const auto pic = particle_handler->particles_in_cell(cell); + for (auto &particle : pic) + { + auto particle_properties = particle.get_properties(); + const double r_particle = + 0.5 * particle_properties[DEM::PropertiesIndex::dp]; + + // Loop over neighboring cells to determine if a given + // neighboring particle contributes to the solid volume of the + // current reference sphere + //*********************************************************************** + for (unsigned int n = 0; n < active_neighbors.size(); n++) + { + // Define the radius of the reference sphere to be used as + // the averaging volume for the QCM, if the reference sphere + // diameter was given by the user the value is already + // defined since it is not dependent on any measure of the + // active cell + if (calculate_reference_sphere_radius) + { + if (void_fraction_parameters + ->qcm_sphere_equal_cell_volume == true) + { + // Get the radius by the volume of sphere which is + // equal to the volume of cell + r_sphere = radius_sphere_volume_cell( + active_neighbors[n]->measure()); + } + else + { + // The radius is obtained from the volume of sphere + // based on R_s = h_omega + r_sphere = + radius_h_omega(active_neighbors[n]->measure()); + } + } + + // Loop over quadrature points + for (unsigned int k = 0; k < n_q_points; ++k) + { + // Distance between particle and quadrature point + double neighbor_distance = + particle.get_location().distance( + neighbor_quadrature_point_location[n][k]); + + // Particle completely in reference sphere + if (neighbor_distance <= (r_sphere - r_particle)) + { + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] += + M_PI * + Utilities::fixed_power( + particle_properties[DEM::PropertiesIndex::dp]) / + (2.0 * dim); + } + + // Particle partially in the reference sphere + else if ((neighbor_distance > (r_sphere - r_particle)) && + (neighbor_distance < (r_sphere + r_particle))) + { + if constexpr (dim == 2) + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] += + particle_circle_intersection_2d( + r_particle, r_sphere, neighbor_distance); + + else if constexpr (dim == 3) + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] += + particle_sphere_intersection_3d( + r_particle, r_sphere, neighbor_distance); + } + + // Particle completely outside reference + // sphere. Do absolutely nothing. + } + } + + // Loop over periodic neighboring cells to determine if a given + // neighboring particle contributes to the solid volume of the + // current reference sphere + //*********************************************************************** + for (unsigned int n = 0; n < active_periodic_neighbors.size(); + n++) + { + if (calculate_reference_sphere_radius) + { + if (void_fraction_parameters + ->qcm_sphere_equal_cell_volume == true) + { + // Get the radius by the volume of sphere which is + // equal to the volume of cell + r_sphere = radius_sphere_volume_cell( + active_periodic_neighbors[n]->measure()); + } + else + { + // The radius is obtained from the volume of sphere + // based on R_s = h_omega + r_sphere = radius_h_omega( + active_periodic_neighbors[n]->measure()); + } + } + + // Loop over quadrature points + for (unsigned int k = 0; k < n_q_points; ++k) + { + // Adjust the location of the particle in the cell to + // account for the periodicity. If the position of the + // periodic cell if greater than the position of the + // current cell, the particle location needs a positive + // correction, and vice versa + const Point particle_location = + (active_periodic_neighbors[n] + ->center()[periodic_direction] > + cell->center()[periodic_direction]) ? + particle.get_location() + periodic_offset : + particle.get_location() - periodic_offset; + + // Distance between particle and quadrature point + double periodic_neighbor_distance = + particle_location.distance( + periodic_neighbor_quadrature_point_location[n][k]); + + // Particle completely in the reference sphere + if (periodic_neighbor_distance <= (r_sphere - r_particle)) + { + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] += + M_PI * + Utilities::fixed_power( + particle_properties[DEM::PropertiesIndex::dp]) / + (2.0 * dim); + } + + // Particle partially in the reference sphere + else if ((periodic_neighbor_distance > + (r_sphere - r_particle)) && + (periodic_neighbor_distance < + (r_sphere + r_particle))) + { + if constexpr (dim == 2) + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] += + particle_circle_intersection_2d( + r_particle, + r_sphere, + periodic_neighbor_distance); + + else if constexpr (dim == 3) + particle_properties + [DEM::PropertiesIndex::volumetric_contribution] += + particle_sphere_intersection_3d( + r_particle, + r_sphere, + periodic_neighbor_distance); + } + + // Particle completely outside the reference + // sphere. Do absolutely nothing. + } + } + } + } + } + + // BB Double check if this is still necessary or not. + // Update ghost particles + // if (load_balance_step) + // { + // particle_handler.sort_particles_into_subdomains_and_cells(); + // particle_handler.exchange_ghost_particles(true); + // } + // else + // { + particle_handler->update_ghost_particles(); + // } + + // After the particles' contributions have been determined, calculate and + // normalize the void fraction + for (const auto &cell : dof_handler.active_cell_iterators()) + { + if (cell->is_locally_owned()) + { + fe_values_void_fraction.reinit(cell); + + local_matrix_void_fraction = 0; + local_rhs_void_fraction = 0; + + double sum_quadrature_weights = 0; + + for (unsigned int q = 0; q < n_q_points; ++q) + { + sum_quadrature_weights += fe_values_void_fraction.JxW(q); + } + + // Define the volume of the reference sphere to be used as the + // averaging volume for the QCM + if (calculate_reference_sphere_radius) + { + if (void_fraction_parameters->qcm_sphere_equal_cell_volume == + true) + { + // Get the radius by the volume of sphere which is + // equal to the volume of cell + r_sphere = radius_sphere_volume_cell(cell->measure()); + } + else + { + // The radius is obtained from the volume of sphere based + // on R_s = h_omega + r_sphere = radius_h_omega(cell->measure()); + } + } + + // Array of real locations for the quadrature points + std::vector> quadrature_point_location; + + quadrature_point_location = + fe_values_void_fraction.get_quadrature_points(); + + // Active neighbors include the current cell as well + auto active_neighbors = + LetheGridTools::find_cells_around_cell(vertices_to_cell, cell); + + // Periodic neighbors of the current cell + auto active_periodic_neighbors = + LetheGridTools::find_cells_around_cell( + vertices_to_periodic_cell, cell); + + for (unsigned int q = 0; q < n_q_points; ++q) + { + particles_volume_in_sphere = 0; + quadrature_void_fraction = 0; + + for (unsigned int m = 0; m < active_neighbors.size(); m++) + { + // Loop over particles in neighbor cell + // Begin and end iterator for particles in neighbor cell + const auto pic = + particle_handler->particles_in_cell(active_neighbors[m]); + for (auto &particle : pic) + { + double distance = 0; + auto particle_properties = particle.get_properties(); + const double r_particle = + particle_properties[DEM::PropertiesIndex::dp] * 0.5; + double single_particle_volume = + M_PI * Utilities::fixed_power(r_particle * 2.0) / + (2 * dim); + + // Distance between particle and quadrature point + // centers + distance = particle.get_location().distance( + quadrature_point_location[q]); + + // Particle completely in the reference sphere + if (distance <= (r_sphere - r_particle)) + particles_volume_in_sphere += + (M_PI * + Utilities::fixed_power( + particle_properties[DEM::PropertiesIndex::dp]) / + (2.0 * dim)) * + single_particle_volume / + particle_properties + [DEM::PropertiesIndex::volumetric_contribution]; + + // Particle partially in the reference sphere + else if ((distance > (r_sphere - r_particle)) && + (distance < (r_sphere + r_particle))) + { + if (dim == 2) + particles_volume_in_sphere += + particle_circle_intersection_2d(r_particle, + r_sphere, + distance) * + single_particle_volume / + particle_properties + [DEM::PropertiesIndex::volumetric_contribution]; + else if (dim == 3) + particles_volume_in_sphere += + particle_sphere_intersection_3d(r_particle, + r_sphere, + distance) * + single_particle_volume / + particle_properties + [DEM::PropertiesIndex::volumetric_contribution]; + } + + // Particle completely outside the reference sphere. Do + // absolutely nothing. + } + } + + // Execute same operations for periodic neighbors, if the + // simulation has no periodic boundaries, the container is + // empty. Also, those operations cannot be done in the previous + // loop because the particles on the periodic side need a + // correction with an offset for the distance with the + // quadrature point + for (unsigned int m = 0; m < active_periodic_neighbors.size(); + m++) + { + // Loop over particles in periodic neighbor cell + const auto pic = particle_handler->particles_in_cell( + active_periodic_neighbors[m]); + for (auto &particle : pic) + { + double distance = 0; + auto particle_properties = particle.get_properties(); + const double r_particle = + particle_properties[DEM::PropertiesIndex::dp] * 0.5; + double single_particle_volume = + M_PI * Utilities::fixed_power(r_particle * 2) / + (2 * dim); + + // Adjust the location of the particle in the cell to + // account for the periodicity. If the position of the + // periodic cell if greater than the position of the + // current cell, the particle location needs a negative + // correction, and vice versa. Since the particle is in + // the periodic cell, this correction is the inverse of + // the correction for the volumetric contribution + const Point particle_location = + (active_periodic_neighbors[m] + ->center()[periodic_direction] > + cell->center()[periodic_direction]) ? + particle.get_location() - periodic_offset : + particle.get_location() + periodic_offset; + + // Distance between particle and quadrature point + // centers + distance = particle_location.distance( + quadrature_point_location[q]); + + // Particle completely in the reference sphere + if (distance <= (r_sphere - r_particle)) + particles_volume_in_sphere += + (M_PI * + Utilities::fixed_power( + particle_properties[DEM::PropertiesIndex::dp]) / + (2.0 * dim)) * + single_particle_volume / + particle_properties + [DEM::PropertiesIndex::volumetric_contribution]; + + // Particle partially in the reference sphere + else if ((distance > (r_sphere - r_particle)) && + (distance < (r_sphere + r_particle))) + { + if (dim == 2) + particles_volume_in_sphere += + particle_circle_intersection_2d(r_particle, + r_sphere, + distance) * + single_particle_volume / + particle_properties + [DEM::PropertiesIndex::volumetric_contribution]; + else if (dim == 3) + particles_volume_in_sphere += + particle_sphere_intersection_3d(r_particle, + r_sphere, + distance) * + single_particle_volume / + particle_properties + [DEM::PropertiesIndex::volumetric_contribution]; + } + + // Particle completely outside the reference sphere. Do + // absolutely nothing. + } + } + + // We use the volume of the cell as it is equal to the volume + // of the sphere + quadrature_void_fraction = + ((fe_values_void_fraction.JxW(q) * cell->measure() / + sum_quadrature_weights) - + particles_volume_in_sphere) / + (fe_values_void_fraction.JxW(q) * cell->measure() / + sum_quadrature_weights); + + for (unsigned int k = 0; k < dofs_per_cell; ++k) + { + phi_vf[k] = fe_values_void_fraction.shape_value(k, q); + grad_phi_vf[k] = fe_values_void_fraction.shape_grad(k, q); + } + + for (unsigned int i = 0; i < dofs_per_cell; ++i) + { + // Assemble L2 projection + // Matrix assembly + for (unsigned int j = 0; j < dofs_per_cell; ++j) + { + local_matrix_void_fraction(i, j) += + ((phi_vf[j] * phi_vf[i]) + + (void_fraction_parameters->l2_smoothing_factor * + grad_phi_vf[j] * grad_phi_vf[i])) * + fe_values_void_fraction.JxW(q); + } + + local_rhs_void_fraction(i) += phi_vf[i] * + quadrature_void_fraction * + fe_values_void_fraction.JxW(q); + } + } + + cell->get_dof_indices(local_dof_indices); + void_fraction_constraints.distribute_local_to_global( + local_matrix_void_fraction, + local_rhs_void_fraction, + local_dof_indices, + system_matrix_void_fraction, + system_rhs_void_fraction); + } + } + + system_matrix_void_fraction.compress(VectorOperation::add); + system_rhs_void_fraction.compress(VectorOperation::add); +} + +template +void +VoidFractionBase::solve_linear_system_and_update_solution( + const bool & /*is_post_mesh_adaptation*/) +{ + // Solve the L2 projection system + const double linear_solver_tolerance = 1e-15; + + if (linear_solver_parameters.verbosity != Parameters::Verbosity::quiet) + { + this->pcout << " -Tolerance of iterative solver is : " + << linear_solver_tolerance << std::endl; + } + + const IndexSet locally_owned_dofs = dof_handler.locally_owned_dofs(); + + GlobalVectorType completely_distributed_solution( + locally_owned_dofs, this->triangulation->get_communicator()); + + SolverControl solver_control(linear_solver_parameters.max_iterations, + linear_solver_tolerance, + true, + true); + + TrilinosWrappers::SolverCG solver(solver_control); + + //********************************************** + // Trillinos Wrapper ILU Preconditioner + //********************************************* + const double ilu_fill = linear_solver_parameters.ilu_precond_fill; + const double ilu_atol = linear_solver_parameters.ilu_precond_atol; + const double ilu_rtol = linear_solver_parameters.ilu_precond_rtol; + + TrilinosWrappers::PreconditionILU::AdditionalData preconditionerOptions( + ilu_fill, ilu_atol, ilu_rtol, 0); + + ilu_preconditioner = std::make_shared(); + + ilu_preconditioner->initialize(system_matrix_void_fraction, + preconditionerOptions); + + solver.solve(system_matrix_void_fraction, + completely_distributed_solution, + system_rhs_void_fraction, + *ilu_preconditioner); + + if (linear_solver_parameters.verbosity != Parameters::Verbosity::quiet) + { + this->pcout << " -Iterative solver took : " << solver_control.last_step() + << " steps " << std::endl; + } + + void_fraction_constraints.distribute(completely_distributed_solution); + void_fraction_locally_relevant = completely_distributed_solution; +} + +// Pre-compile the 2D and 3D VoidFractionBase solver to ensure that the +// library is valid before we actually compile the solver This greatly +// helps with debugging +template class VoidFractionBase<2>; +template class VoidFractionBase<3>;