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

Generated by: LCOV version 1.14