SpECTRE Documentation Coverage Report
Current view: top level - NumericalAlgorithms/LinearOperators/Filters - FilledCylinder.hpp Hit Total Coverage
Commit: c3e43f8d41800b0ecefb9d1393f1de1d5a280c8f Lines: 19 73 26.0 %
Date: 2026-07-24 22:09:25
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : #pragma once
       5             : 
       6             : #include <cstddef>
       7             : #include <memory>
       8             : #include <optional>
       9             : #include <string>
      10             : #include <unordered_map>
      11             : #include <unordered_set>
      12             : #include <vector>
      13             : 
      14             : #include "DataStructures/DataVector.hpp"
      15             : #include "DataStructures/Matrix.hpp"
      16             : #include "DataStructures/Tensor/TypeAliases.hpp"
      17             : #include "NumericalAlgorithms/LinearOperators/Filters/Filter.hpp"
      18             : #include "Options/Auto.hpp"
      19             : #include "Options/Context.hpp"
      20             : #include "Options/String.hpp"
      21             : #include "Utilities/Serialization/CharmPupable.hpp"
      22             : #include "Utilities/TMPL.hpp"
      23             : 
      24             : /// \cond
      25             : template <size_t Dim>
      26             : class Mesh;
      27             : template <typename TagsList>
      28             : class Variables;
      29             : namespace PUP {
      30             : class er;
      31             : }  // namespace PUP
      32             : namespace gsl {
      33             : template <typename T>
      34             : class not_null;
      35             : }  // namespace gsl
      36             : /// \endcond
      37             : 
      38             : namespace Filters {
      39             : /*!
      40             :  * \ingroup DiscontinuousGalerkinGroup
      41             :  * \brief A modal filter for filled-cylinder elements: a smooth exponential
      42             :  * roll-off of the coupled ZernikeB2 radial-angular (disk) modes and an
      43             :  * independent exponential roll-off in the axial direction, plus an optional
      44             :  * top-mode Heaviside cutoff in the angular direction.
      45             :  *
      46             :  * Concrete implementation of `Filters::Filter` for the central filled-cylinder
      47             :  * block created by `domain::creators::AngularCylinder`, driven by the DG
      48             :  * filtering action. See `Filters::Filter` for the framing of volume vs.
      49             :  * boundary application, the substep / every-N-steps cadence controls, and the
      50             :  * `blocks_to_filter` semantics.
      51             :  *
      52             :  * A filled-cylinder element uses a ZernikeB2 basis in the radial direction
      53             :  * (logical dimension 0), a ZernikeB2 basis in the angular direction (logical
      54             :  * dimension 1), and a Legendre/Chebyshev basis in the axial \f$z\f$ direction
      55             :  * (logical dimension 2). Unlike the Fourier-angular `Filters::HollowCylinder`
      56             :  * shells, the ZernikeB2 radial and angular spectral spaces are intertwined (a
      57             :  * two-dimensional disk basis), so the radial and angular roll-offs cannot be
      58             :  * specified independently: a single `RadialAngularHalfPower` drives the
      59             :  * combined disk exponential filter, while `ZHalfPower` drives the axial filter.
      60             :  *
      61             :  * In the volume the disk and axial filters are applied by
      62             :  * `Spectral::filtering::zernike_b2_cylinder_filter`. The coefficient of the
      63             :  * exponential roll-off is hardcoded to 36, matching `Filters::Hypercube`,
      64             :  * `Filters::SphericalShell`, and `Filters::HollowCylinder`. When
      65             :  * `NumModesToKill` is nonzero, the top `NumModesToKill` angular (Fourier
      66             :  * \f$m\f$) modes are additionally set to zero by a Heaviside cutoff (the
      67             :  * \f$m=0\f$ mode is always retained).
      68             :  *
      69             :  * For boundary-correction filtering the face mesh is classified by basis and
      70             :  * quadrature:
      71             :  * - an axial face (both directions ZernikeB2) is a full disk and is filtered by
      72             :  *   `Spectral::filtering::zernike_b2_disk_filter`;
      73             :  * - a mantle/radial face (ZernikeB2 with `Equiangular` quadrature in the
      74             :  *   angular direction, Legendre/Chebyshev in \f$z\f$) is filtered by treating
      75             :  *   the equiangular angular direction as a Fourier direction (an exponential
      76             :  *   roll-off using `RadialAngularHalfPower` plus the `NumModesToKill` cutoff),
      77             :  *   together with the axial filter;
      78             :  */
      79             : template <typename TagList>
      80           1 : class FilledCylinder : public Filter<3, TagList> {
      81             :  public:
      82             :   /// \brief The number of top angular (Fourier) \f$m\f$-modes to set to zero.
      83           1 :   struct NumModesToKill {
      84           0 :     using type = size_t;
      85           0 :     static constexpr Options::String help =
      86             :         "The number of top angular (Fourier) m-modes to set to zero.";
      87             :   };
      88             : 
      89             :   /*!
      90             :    * \brief Half of the exponent \f$m\f$ in the smooth exponential roll-off
      91             :    * applied to the coupled ZernikeB2 radial-angular (disk) modal coefficients
      92             :    * (logical dimensions 0 and 1).
      93             :    *
      94             :    * \f{align*}{
      95             :    *  c \to c \exp\left[-36 \left(\frac{n}{N_r}\right)^{2m}\right]
      96             :    *           \exp\left[-36 \left(\frac{k}{K}\right)^{2m}\right]
      97             :    * \f}
      98             :    *
      99             :    * where \f$n\f$ and \f$k\f$ are the radial and angular mode numbers and
     100             :    * \f$N_r\f$, \f$K\f$ are the corresponding maximum resolved modes. If `None`,
     101             :    * only the top-mode Heaviside cutoff (if any) is applied to the disk modes.
     102             :    */
     103           1 :   struct RadialAngularHalfPower {
     104           0 :     using type = Options::Auto<size_t, Options::AutoLabel::None>;
     105           0 :     static constexpr Options::String help =
     106             :         "The half-power for the coupled ZernikeB2 radial-angular (disk) "
     107             :         "exponential roll-off. If None, only the top-mode cutoff is applied to "
     108             :         "the disk modes.";
     109             :   };
     110             : 
     111             :   /*!
     112             :    * \brief Half of the exponent \f$m\f$ in the smooth exponential roll-off
     113             :    * applied to the axial \f$z\f$ modal coefficients (logical dimension 2). If
     114             :    * `None`, the axial direction is not filtered.
     115             :    */
     116           1 :   struct ZHalfPower {
     117           0 :     using type = Options::Auto<size_t, Options::AutoLabel::None>;
     118           0 :     static constexpr Options::String help =
     119             :         "The half-power for the axial (z) exponential filter. If None, no "
     120             :         "axial filtering is applied.";
     121             :   };
     122             : 
     123             :   /// \brief Enable (true) or disable (false) the filter
     124           1 :   struct Enable {
     125           0 :     using type = bool;
     126           0 :     static constexpr Options::String help = {"Enable the filter"};
     127             :   };
     128             : 
     129             :   /// \brief Which blocks the filter should be applied to.
     130           1 :   struct BlocksToFilter {
     131           0 :     using type =
     132             :         Options::Auto<std::vector<std::string>, Options::AutoLabel::All>;
     133           0 :     static constexpr Options::String help = {
     134             :         "List of blocks or block groups to apply filtering to. All other "
     135             :         "blocks will have no filtering. You can also specify 'All' to do "
     136             :         "filtering in all blocks of the domain that are filled cylinders."};
     137             :   };
     138             : 
     139             :   /// \brief Apply the volume filter inside every Runge-Kutta substep
     140             :   /// instead of only at whole-step boundaries.
     141           1 :   struct VolumeFilterOnSubstep {
     142           0 :     using type = bool;
     143           0 :     static constexpr Options::String help = {
     144             :         "Enable the volume filter on every substep."};
     145             :   };
     146             : 
     147             :   /// \brief Apply the boundary correction filter inside every Runge-Kutta
     148             :   /// substep instead of only at whole-step boundaries.
     149           1 :   struct BoundaryCorrectionFilterOnSubstep {
     150           0 :     using type = bool;
     151           0 :     static constexpr Options::String help = {
     152             :         "Enable the boundary filter on every substep."};
     153             :   };
     154             : 
     155             :   /// \brief Apply the volume filter once every `N` steps. `None`
     156             :   /// (`std::nullopt`) disables the every-N-steps trigger.
     157             :   ///
     158             :   /// \note Currently the check for whether to filter on every `N` steps is done
     159             :   /// relative to the start of the current Slab. This means that for GTS,
     160             :   /// independent of the value of `N` for every `N` steps, every step has a
     161             :   /// filter applied since GTS has one step per slab.
     162           1 :   struct VolumeFilterEveryNSteps {
     163           0 :     using type = Options::Auto<size_t, Options::AutoLabel::None>;
     164           0 :     static constexpr Options::String help = {
     165             :         "Enable the volume filter on every N steps. 'None' to disable."};
     166             :   };
     167             : 
     168             :   /// \brief Apply the boundary correction filter once every `N` steps. `None`
     169             :   /// (`std::nullopt`) disables the every-N-steps trigger.
     170             :   ///
     171             :   /// \note Currently the check for whether to filter on every `N` steps is done
     172             :   /// relative to the start of the current Slab. This means that for GTS,
     173             :   /// independent of the value of `N` for every `N` steps, every step has a
     174             :   /// filter applied since GTS has one step per slab.
     175           1 :   struct BoundaryCorrectionFilterEveryNSteps {
     176           0 :     using type = Options::Auto<size_t, Options::AutoLabel::None>;
     177           0 :     static constexpr Options::String help = {
     178             :         "Enable the boundary filter on every N steps. 'None' to disable."};
     179             :   };
     180             : 
     181           0 :   using options =
     182             :       tmpl::list<NumModesToKill, RadialAngularHalfPower, ZHalfPower, Enable,
     183             :                  BlocksToFilter, VolumeFilterOnSubstep,
     184             :                  BoundaryCorrectionFilterOnSubstep, VolumeFilterEveryNSteps,
     185             :                  BoundaryCorrectionFilterEveryNSteps>;
     186             : 
     187           0 :   static constexpr Options::String help = {
     188             :       "A filled-cylinder filter applying a smooth exponential roll-off of the "
     189             :       "coupled ZernikeB2 radial-angular (disk) modes and an independent "
     190             :       "exponential roll-off in the axial direction, with an optional top-mode "
     191             :       "Heaviside cutoff in the angular direction."};
     192             : 
     193           0 :   FilledCylinder() = default;
     194             : 
     195           0 :   FilledCylinder(
     196             :       size_t num_modes_to_kill, std::optional<size_t> radial_angular_half_power,
     197             :       std::optional<size_t> z_half_power, bool enable,
     198             :       const std::optional<std::vector<std::string>>& blocks_to_filter,
     199             :       bool volume_filter_on_substep, bool boundary_filter_on_substep,
     200             :       std::optional<size_t> volume_filter_every_n_steps,
     201             :       std::optional<size_t> boundary_filter_every_n_steps,
     202             :       const Options::Context& context = {});
     203             : 
     204           0 :   WRAPPED_PUPable_decl_base_template(  // NOLINT
     205             :       SINGLE_ARG(Filter<3, TagList>), FilledCylinder);
     206           0 :   explicit FilledCylinder(CkMigrateMessage* msg) : Filter<3, TagList>(msg) {}
     207             : 
     208             :   // NOLINTNEXTLINE(google-runtime-references)
     209           0 :   void pup(PUP::er& p) override;
     210             : 
     211           1 :   std::unique_ptr<Filter<3, TagList>> get_clone() const override;
     212             : 
     213           1 :   bool apply_volume_filter_on_substep() const override;
     214           1 :   bool apply_volume_filter_on_this_step(size_t step_number) const override;
     215             : 
     216           1 :   bool apply_boundary_filter_on_substep() const override;
     217           1 :   bool apply_boundary_filter_on_this_step(size_t step_number) const override;
     218             : 
     219           1 :   bool need_jacobians() const override { return false; }
     220             : 
     221           0 :   bool supports_mesh(const Mesh<3>& mesh) const override;
     222             : 
     223           1 :   std::string name() const override { return "FilledCylinder"; }
     224             : 
     225           1 :   const std::optional<std::vector<size_t>>& blocks_to_filter() const override;
     226             : 
     227           1 :   void set_blocks_to_filter(
     228             :       const std::vector<std::string>& all_block_names,
     229             :       const std::unordered_map<std::string, std::unordered_set<std::string>>&
     230             :           block_groups) override;
     231             : 
     232           0 :   void apply_in_volume(
     233             :       gsl::not_null<Variables<TagList>*> vars, const Mesh<3>& mesh,
     234             :       const std::optional<
     235             :           InverseJacobian<DataVector, 3, Frame::Grid, Frame::Inertial>>&
     236             :           inv_jac_grid_to_inertial,
     237             :       const std::optional<
     238             :           Jacobian<DataVector, 3, Frame::Grid, Frame::Inertial>>&
     239             :           jac_grid_to_inertial) const override;
     240             : 
     241           0 :   void apply_on_boundary(
     242             :       gsl::not_null<Variables<TagList>*> vars, const Mesh<2>& mesh,
     243             :       const std::optional<
     244             :           InverseJacobian<DataVector, 3, Frame::Grid, Frame::Inertial>>&
     245             :           inv_jac_grid_to_inertial,
     246             :       const std::optional<
     247             :           Jacobian<DataVector, 3, Frame::Grid, Frame::Inertial>>&
     248             :           jac_grid_to_inertial) const override;
     249             : 
     250           0 :   bool is_equal(const Filter<3, TagList>& other) const override;
     251             : 
     252             :  private:
     253             :   template <typename LocalTagList>
     254             :   // NOLINTNEXTLINE(readability-redundant-declaration)
     255           0 :   friend bool operator==(const FilledCylinder<LocalTagList>& lhs,
     256             :                          const FilledCylinder<LocalTagList>& rhs);
     257             : 
     258             :   // Caches a single filter matrix together with the parameters it was built
     259             :   // from: the 1-D extent, the half-power, and (for the angular direction) the
     260             :   // number of top modes killed. Rebuilt (replacing the previous entry) whenever
     261             :   // queried with any different parameter, keeping the memory footprint bounded
     262             :   // instead of growing with the number of distinct extents encountered.
     263           0 :   struct SingleExtentCache {
     264           0 :     std::optional<size_t> extent{std::nullopt};
     265           0 :     std::optional<size_t> half_power{std::nullopt};
     266           0 :     size_t num_modes_to_kill{0};
     267           0 :     Matrix matrix{};
     268             :   };
     269             : 
     270             :   // Returns the cached axial exponential filter matrix for a Legendre or
     271             :   // Chebyshev direction with the given half-power and 1-D mesh, stored in the
     272             :   // passed-in cache. Returns an empty (identity) matrix when the half-power is
     273             :   // None.
     274           0 :   const Matrix& exponential_filter_matrix(std::optional<size_t> half_power,
     275             :                                           const Mesh<1>& mesh_1d,
     276             :                                           SingleExtentCache& cache) const;
     277             : 
     278             :   // Returns the cached angular (Fourier) filter matrix combining the optional
     279             :   // exponential roll-off and the optional top-mode cutoff. On the mantle face
     280             :   // the equiangular ZernikeB2-angular direction is treated as Fourier, so the
     281             :   // caller passes a Fourier/Equiangular `Mesh<1>` with the matching extent.
     282             :   // Returns an empty (identity) matrix when neither is requested.
     283           0 :   const Matrix& angular_filter_matrix(const Mesh<1>& mesh_1d) const;
     284             : 
     285           0 :   size_t num_modes_to_kill_{0};
     286           0 :   std::optional<size_t> radial_angular_half_power_{std::nullopt};
     287           0 :   std::optional<size_t> z_half_power_{std::nullopt};
     288           0 :   bool enable_{true};
     289           0 :   std::optional<std::vector<std::string>> blocks_and_groups_to_filter_{};
     290           0 :   std::optional<std::vector<size_t>> blocks_to_filter_{};
     291           0 :   bool volume_filter_on_substep_{false};
     292           0 :   bool boundary_filter_on_substep_{false};
     293           0 :   std::optional<size_t> volume_filter_every_n_steps_{std::nullopt};
     294           0 :   std::optional<size_t> boundary_filter_every_n_steps_{std::nullopt};
     295             : 
     296             :   // Boundary-filter matrix caches, each bound to the single extent it was built
     297             :   // for and rebuilt when queried with a different extent. The angular filter
     298             :   // is used only by `apply_on_boundary` right now: the radial and angular
     299             :   // filter is applied uncached by
     300             :   // `Spectral::filtering::zernike_b2_cylinder_filter`, which intertwines the
     301             :   // radial and angular disk modes and so cannot reuse a per-direction 1-D
     302             :   // matrix. `cached_z_filter_` holds the axial (Legendre/Chebyshev) filter and
     303             :   // `cached_angular_filter_` the Fourier-treated mantle-angular filter. The
     304             :   // empty matrix is returned (and not cached) when a direction is not filtered.
     305             :   // NOLINTNEXTLINE(spectre-mutable)
     306           0 :   mutable SingleExtentCache cached_z_filter_{};
     307             :   // NOLINTNEXTLINE(spectre-mutable)
     308           0 :   mutable SingleExtentCache cached_angular_filter_{};
     309             :   // Returned by const reference to represent the identity for unfiltered
     310             :   // directions; never mutated, so it needs no `mutable`.
     311           0 :   Matrix empty_matrix_{};
     312             :   // Buffer used internally by `Spectral::filtering::zernike_b2_cylinder_filter`
     313             :   // NOLINTNEXTLINE(spectre-mutable)
     314           0 :   mutable DataVector temp_storage_{};
     315             : };
     316             : 
     317             : template <typename TagList>
     318           0 : bool operator==(const FilledCylinder<TagList>& lhs,
     319             :                 const FilledCylinder<TagList>& rhs);
     320             : 
     321             : template <typename TagList>
     322           0 : bool operator!=(const FilledCylinder<TagList>& lhs,
     323             :                 const FilledCylinder<TagList>& rhs);
     324             : }  // namespace Filters

Generated by: LCOV version 1.14