From e6738a3de7d32af471d27b5f52d04929d72b8a3e Mon Sep 17 00:00:00 2001 From: vincmarks Date: Wed, 6 May 2026 18:49:26 +0200 Subject: [PATCH 1/9] swap between mesh types --- docs/make.jl | 4 +- .../src/meshes/mesh_constructor_comparison.md | 115 +++++++++++++++++ .../elixir_advection_mesh_swap.jl | 87 +++++++++++++ src/meshes/p4est_mesh.jl | 117 ++++++++++++++++++ src/meshes/structured_mesh.jl | 58 +++++++++ src/meshes/t8code_mesh.jl | 97 +++++++++++++++ src/solvers/dgmulti/types.jl | 39 ++++++ test/test_p4est_2d.jl | 19 +++ test/test_unit.jl | 65 ++++++++++ 9 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 docs/src/meshes/mesh_constructor_comparison.md create mode 100644 examples/special_elixirs/elixir_advection_mesh_swap.jl diff --git a/docs/make.jl b/docs/make.jl index b138519d93f..c9885561004 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -151,7 +151,9 @@ makedocs( "Structured mesh" => joinpath("meshes", "structured_mesh.md"), "Unstructured mesh" => joinpath("meshes", "unstructured_quad_mesh.md"), "P4est-based mesh" => joinpath("meshes", "p4est_mesh.md"), - "DGMulti mesh" => joinpath("meshes", "dgmulti_mesh.md") + "DGMulti mesh" => joinpath("meshes", "dgmulti_mesh.md"), + "Mesh constructor comparison" => joinpath("meshes", + "mesh_constructor_comparison.md") ], "Time integration" => "time_integration.md", "Callbacks" => "callbacks.md", diff --git a/docs/src/meshes/mesh_constructor_comparison.md b/docs/src/meshes/mesh_constructor_comparison.md new file mode 100644 index 00000000000..330c68895f0 --- /dev/null +++ b/docs/src/meshes/mesh_constructor_comparison.md @@ -0,0 +1,115 @@ +# Unified mesh constructor interface + +Trixi.jl provides several mesh types suited for different scenarios. +It may be useful to quickly swap between mesh types +without having to rewrite the mesh construction call. + +An example demonstrating mesh-type swapping for the same simulation setup is +provided in `examples/special_elixirs/elixir_advection_mesh_swap.jl`. + +## Constructor overview + +All structured mesh types support three equivalent styles for rectangular domains: + +### Style 1 — `cells_per_dimension` positional (classic) + +```julia +StructuredMesh((16, 16), (-1.0, -1.0), (1.0, 1.0)) +P4estMesh((16, 16), (-1.0, -1.0), (1.0, 1.0); polydeg = 3) +T8codeMesh((16, 16), (-1.0, -1.0), (1.0, 1.0)) +DGMultiMesh(dg, (16, 16); + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +``` + +### Style 2 — `initial_refinement_level` (like `TreeMesh`) + +Directly mirrors the [`TreeMesh`](@ref) interface, yielding `2^initial_refinement_level` +cells per dimension: + +```julia +# Original TreeMesh call +mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0); + initial_refinement_level = 4, + n_cells_max = 30_000) + +# Drop-in replacements — only n_cells_max needs to be removed: +mesh = StructuredMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 4) +mesh = P4estMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 4, polydeg = 3) +mesh = T8codeMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 4) +mesh = DGMultiMesh(dg; initial_refinement_level = 4, + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +``` + +Note: for `StructuredMesh` and `DGMultiMesh`, `initial_refinement_level` directly sets +`cells_per_dimension = 2^initial_refinement_level`. For `P4estMesh` and `T8codeMesh`, +a single tree per dimension is created and refined `initial_refinement_level` times, +which also yields `2^initial_refinement_level` leaf cells per dimension. + +### Style 3 — keyword-based (like `P4estMesh`) + +All parameters as keywords, same style as the original [`P4estMesh`](@ref) interface: + +```julia +# Original P4estMesh keyword call +mesh = P4estMesh((16, 16); polydeg = 3, + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) + +# Equivalent calls on other mesh types: +mesh = StructuredMesh((16, 16); + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +mesh = T8codeMesh((16, 16); + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +mesh = DGMultiMesh(dg; cells_per_dimension = (16, 16), + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +``` + +## Mapping and face-based constructors + +For curvilinear meshes, `mapping` and `faces` can also be passed positionally, +matching the [`StructuredMesh`](@ref) interface: + +```julia +# All three are equivalent: +StructuredMesh((16, 16), mapping) +P4estMesh((16, 16), mapping; polydeg = 3) +T8codeMesh((16, 16), mapping) + +# Face-based: +StructuredMesh((16, 16), faces) +P4estMesh((16, 16), faces; polydeg = 3) +T8codeMesh((16, 16), faces) +``` + +## Notes on `TreeMesh` + +[`TreeMesh`](@ref) has a fundamentally different design and cannot be used as a +drop-in target in all cases: + +- It requires `n_cells_max` with no equivalent in other mesh types. +- It only supports refinement where all dimensions have the same number of cells (`2^level`). +- It only supports rectangular domains — no `mapping` or `faces`. + +When swapping **from** `TreeMesh` to another type, remove `n_cells_max` and +choose `initial_refinement_level` or compute `cells_per_dimension = (2^level, 2^level, ...)` manually. + +When swapping **to** `TreeMesh` from another type, the swap is only possible +if the domain is rectangular and `cells_per_dimension` is a power of two with the same value in all dimensions. + +## `trees_per_dimension` vs. `cells_per_dimension` + +In [`P4estMesh`](@ref) and [`T8codeMesh`](@ref), the first positional argument is named +`trees_per_dimension`, reflecting the p4est concept of a *forest of trees*: the argument +specifies how many trees exist per dimension, each of which may be further refined +by `initial_refinement_level`. The total leaf-cell count per dimension is therefore +`trees_per_dimension * 2^initial_refinement_level`. + +In contrast, [`StructuredMesh`](@ref) and [`DGMultiMesh`](@ref) use `cells_per_dimension` +for the final cell count with no further refinement. + +The Style 1 convenience constructors (`P4estMesh(cells_per_dimension, coordinates_min, ...)`) +pass `cells_per_dimension` directly as `trees_per_dimension` with `initial_refinement_level = 0` +by default, so `cells_per_dimension` equals the actual leaf-cell count per dimension. + +The Style 2 convenience constructors (`P4estMesh(coordinates_min, coordinates_max; initial_refinement_level=...)`) +use `trees_per_dimension = (1, 1, ...)` internally and forward `initial_refinement_level`, +so the leaf-cell count per dimension is `2^initial_refinement_level` — consistent with `TreeMesh`. diff --git a/examples/special_elixirs/elixir_advection_mesh_swap.jl b/examples/special_elixirs/elixir_advection_mesh_swap.jl new file mode 100644 index 00000000000..461d50c7981 --- /dev/null +++ b/examples/special_elixirs/elixir_advection_mesh_swap.jl @@ -0,0 +1,87 @@ +# Demonstrates the unified mesh constructor interface for rectangular domains. +# The same 2D linear advection setup is run with different mesh types and +# constructor styles using equivalent calls. +# +# See docs/src/meshes/mesh_constructor_comparison.md for more details + +using OrdinaryDiffEqLowStorageRK +using Trixi + +############################################################################### +# Parameters + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +# The solution polynomial degree here is only used by the solver and independent of the mesh geometry +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +initial_refinement_level = 4 # 2^4 = 16 cells per dimension + +t_end = 1.0 + +############################################################################### +# Helper function: build semi and solve for a mesh +# Based on: examples/tree_2d_dgsem/elixir_advection_basic.jl + +function run_advection(mesh) + semi = SemidiscretizationHyperbolic(mesh, equations, + initial_condition_convergence_test, solver; + boundary_conditions = boundary_condition_periodic) + ode = semidiscretize(semi, (0.0, t_end)) + stepsize_callback = StepsizeCallback(cfl = 1.6) + sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); + dt = 1.0, ode_default_options()..., + callback = CallbackSet(stepsize_callback)) + return sol +end + +############################################################################### +# initial_refinement_level (like TreeMesh) + +# Original TreeMesh call (for reference): +mesh = TreeMesh(coordinates_min, coordinates_max; + initial_refinement_level = initial_refinement_level, + n_cells_max = 30_000, periodicity = true) +sol = run_advection(mesh) + +# Drop-in replacements — only n_cells_max needs to be removed: +mesh = StructuredMesh(coordinates_min, coordinates_max; + initial_refinement_level = initial_refinement_level, + periodicity = true) +sol = run_advection(mesh) + +# polydeg here controls the geometry interpolation degree of the mesh +mesh = P4estMesh(coordinates_min, coordinates_max; + initial_refinement_level = initial_refinement_level, + polydeg = 1, periodicity = true) +sol = run_advection(mesh) + +############################################################################### +# cells_per_dimension positional + +cpd = ntuple(_ -> 2^initial_refinement_level, 2) # (16, 16) + +mesh = StructuredMesh(cpd, coordinates_min, coordinates_max; periodicity = true) +sol = run_advection(mesh) + +mesh = P4estMesh(cpd, coordinates_min, coordinates_max; polydeg = 1, periodicity = true) +sol = run_advection(mesh) + +############################################################################### +# keyword-based + +mesh = StructuredMesh(cpd; + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + periodicity = true) +sol = run_advection(mesh) + +mesh = P4estMesh(cpd; + polydeg = 1, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + periodicity = true) +sol = run_advection(mesh) diff --git a/src/meshes/p4est_mesh.jl b/src/meshes/p4est_mesh.jl index ae099d601ed..34ed4fae8c5 100644 --- a/src/meshes/p4est_mesh.jl +++ b/src/meshes/p4est_mesh.jl @@ -245,6 +245,123 @@ function P4estMesh(trees_per_dimension; polydeg, p4est_partition_allow_for_coarsening) end +# Convenience constructors matching the positional interface of StructuredMesh + +""" + P4estMesh(cells_per_dimension, coordinates_min, coordinates_max; polydeg, kwargs...) + +Convenience constructor for a rectangular `P4estMesh`. Matches the positional interface of +`StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max)` for easy mesh-type swapping. +""" +function P4estMesh(cells_per_dimension, coordinates_min, coordinates_max; + polydeg, + RealT = Float64, + initial_refinement_level = 0, + periodicity = false, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true) + return P4estMesh(cells_per_dimension; + polydeg = polydeg, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity, + unsaved_changes = unsaved_changes, + p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) +end + +""" + P4estMesh(cells_per_dimension, mapping::Function; polydeg, kwargs...) + +Convenience constructor for a curved `P4estMesh`. Matches the positional interface of +`StructuredMesh(cells_per_dimension, mapping)` for easy mesh-type swapping. +""" +function P4estMesh(cells_per_dimension, mapping::Function; + polydeg, + RealT = Float64, + initial_refinement_level = 0, + periodicity = false, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true) + return P4estMesh(cells_per_dimension; + polydeg = polydeg, + mapping = mapping, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity, + unsaved_changes = unsaved_changes, + p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) +end + +""" + P4estMesh(cells_per_dimension, faces::Tuple; polydeg, kwargs...) + +Convenience constructor for a face-parametrized `P4estMesh`. Matches the positional interface of +`StructuredMesh(cells_per_dimension, faces)` for easy mesh-type swapping. +""" +function P4estMesh(cells_per_dimension, faces::Tuple; + polydeg, + RealT = Float64, + initial_refinement_level = 0, + periodicity = false, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true) + return P4estMesh(cells_per_dimension; + polydeg = polydeg, + faces = faces, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity, + unsaved_changes = unsaved_changes, + p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) +end + +# TreeMesh-compatible constructors: accept (coordinates_min, coordinates_max; initial_refinement_level) + +""" + P4estMesh(coordinates_min, coordinates_max; initial_refinement_level, polydeg, kwargs...) + +Create a rectangular `P4estMesh` from `coordinates_min`/`coordinates_max` and +`initial_refinement_level`, using the same interface as `TreeMesh` for easy mesh-type swapping. +Creates a single tree per dimension that is uniformly refined `initial_refinement_level` times, +yielding `2^initial_refinement_level` cells per dimension. +""" +function P4estMesh(coordinates_min::NTuple{NDIMS}, coordinates_max::NTuple{NDIMS}; + initial_refinement_level, + polydeg, + RealT = Float64, + periodicity = false, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true) where {NDIMS} + return P4estMesh(ntuple(_ -> 1, NDIMS); + polydeg = polydeg, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity, + unsaved_changes = unsaved_changes, + p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) +end + +# 1D convenience +function P4estMesh(coordinates_min::Real, coordinates_max::Real; + initial_refinement_level, + polydeg, + RealT = Float64, + periodicity = false, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true) + return P4estMesh((coordinates_min,), (coordinates_max,); + initial_refinement_level = initial_refinement_level, + polydeg = polydeg, + RealT = RealT, + periodicity = periodicity, + unsaved_changes = unsaved_changes, + p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) +end + # 2D version function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{2}, periodicity) diff --git a/src/meshes/structured_mesh.jl b/src/meshes/structured_mesh.jl index 716015d746e..09509c96458 100644 --- a/src/meshes/structured_mesh.jl +++ b/src/meshes/structured_mesh.jl @@ -145,6 +145,64 @@ function StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; mapping_as_string = mapping_as_string) end +""" + StructuredMesh(coordinates_min, coordinates_max; initial_refinement_level, periodicity=false) + +Create a rectangular `StructuredMesh` from `coordinates_min`/`coordinates_max` and +`initial_refinement_level`, using the same interface as `TreeMesh` for easy mesh-type swapping. +The number of cells per dimension is `2^initial_refinement_level`. +""" +function StructuredMesh(coordinates_min::NTuple{NDIMS}, + coordinates_max::NTuple{NDIMS}; + initial_refinement_level, + periodicity = false) where {NDIMS} + cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) + return StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; + periodicity = periodicity) +end + +# 1D convenience +function StructuredMesh(coordinates_min::Real, coordinates_max::Real; + initial_refinement_level, + periodicity = false) + return StructuredMesh((coordinates_min,), (coordinates_max,); + initial_refinement_level = initial_refinement_level, + periodicity = periodicity) +end + +""" + StructuredMesh(cells_per_dimension; mapping=nothing, faces=nothing, + coordinates_min=nothing, coordinates_max=nothing, + RealT=Float64, periodicity=false) + +Convenience constructor for `StructuredMesh` using a keyword-based interface, matching the style +of `P4estMesh(cells_per_dimension; ...)` for easy mesh-type swapping. + +Exactly one of `mapping`, `faces`, or `coordinates_min`/`coordinates_max` must be specified. +""" +function StructuredMesh(cells_per_dimension; + mapping = nothing, + faces = nothing, + coordinates_min = nothing, + coordinates_max = nothing, + RealT = Float64, + periodicity = false) + @assert count(i -> i !== nothing, + (mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified" + @assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified" + + if faces !== nothing + return StructuredMesh(cells_per_dimension, faces; RealT = RealT, + periodicity = periodicity) + elseif coordinates_min !== nothing + return StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; + periodicity = periodicity) + else + return StructuredMesh(cells_per_dimension, mapping; RealT = RealT, + periodicity = periodicity) + end +end + # Extract a string of the code that defines the mapping function function mapping2string(mapping, ndims, RealT = Float64) return string(code_string(mapping, ntuple(_ -> RealT, ndims))) diff --git a/src/meshes/t8code_mesh.jl b/src/meshes/t8code_mesh.jl index 9cfbe7016a4..52c34e4467f 100644 --- a/src/meshes/t8code_mesh.jl +++ b/src/meshes/t8code_mesh.jl @@ -505,6 +505,103 @@ function T8codeMesh(trees_per_dimension; polydeg = 1, mapping = mapping_) end +# Convenience constructors matching the positional interface of StructuredMesh + +""" + T8codeMesh(cells_per_dimension, coordinates_min, coordinates_max; polydeg=1, kwargs...) + +Convenience constructor for a rectangular `T8codeMesh`. Matches the positional interface of +`StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max)` for easy mesh-type swapping. +""" +function T8codeMesh(cells_per_dimension, coordinates_min, coordinates_max; + polydeg = 1, + RealT = Float64, + initial_refinement_level = 0, + periodicity = false) + return T8codeMesh(cells_per_dimension; + polydeg = polydeg, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity) +end + +""" + T8codeMesh(cells_per_dimension, mapping::Function; polydeg=1, kwargs...) + +Convenience constructor for a curved `T8codeMesh`. Matches the positional interface of +`StructuredMesh(cells_per_dimension, mapping)` for easy mesh-type swapping. +""" +function T8codeMesh(cells_per_dimension, mapping::Function; + polydeg = 1, + RealT = Float64, + initial_refinement_level = 0, + periodicity = false) + return T8codeMesh(cells_per_dimension; + polydeg = polydeg, + mapping = mapping, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity) +end + +""" + T8codeMesh(cells_per_dimension, faces::Tuple; polydeg=1, kwargs...) + +Convenience constructor for a face-parametrized `T8codeMesh`. Matches the positional interface of +`StructuredMesh(cells_per_dimension, faces)` for easy mesh-type swapping. +""" +function T8codeMesh(cells_per_dimension, faces::Tuple; + polydeg = 1, + RealT = Float64, + initial_refinement_level = 0, + periodicity = false) + return T8codeMesh(cells_per_dimension; + polydeg = polydeg, + faces = faces, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity) +end + +# TreeMesh-compatible constructors: accept (coordinates_min, coordinates_max; initial_refinement_level) + +""" + T8codeMesh(coordinates_min, coordinates_max; initial_refinement_level, polydeg=1, kwargs...) + +Create a rectangular `T8codeMesh` from `coordinates_min`/`coordinates_max` and +`initial_refinement_level`, using the same interface as `TreeMesh` for easy mesh-type swapping. +Creates a single tree per dimension that is uniformly refined `initial_refinement_level` times, +yielding `2^initial_refinement_level` cells per dimension. +""" +function T8codeMesh(coordinates_min::NTuple{NDIMS}, coordinates_max::NTuple{NDIMS}; + initial_refinement_level, + polydeg = 1, + RealT = Float64, + periodicity = false) where {NDIMS} + return T8codeMesh(ntuple(_ -> 1, NDIMS); + polydeg = polydeg, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + RealT = RealT, + initial_refinement_level = initial_refinement_level, + periodicity = periodicity) +end + +# 1D convenience +function T8codeMesh(coordinates_min::Real, coordinates_max::Real; + initial_refinement_level, + polydeg = 1, + RealT = Float64, + periodicity = false) + return T8codeMesh((coordinates_min,), (coordinates_max,); + initial_refinement_level = initial_refinement_level, + polydeg = polydeg, + RealT = RealT, + periodicity = periodicity) +end + """ T8codeMesh(cmesh::Ptr{t8_cmesh}, mapping=nothing, polydeg=1, RealT=Float64, diff --git a/src/solvers/dgmulti/types.jl b/src/solvers/dgmulti/types.jl index abf9eb1d9b0..bd06652587a 100644 --- a/src/solvers/dgmulti/types.jl +++ b/src/solvers/dgmulti/types.jl @@ -298,6 +298,45 @@ function DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension; return DGMultiMesh(dg, GeometricTermsType(Cartesian(), dg), md, boundary_faces) end +""" + DGMultiMesh(dg::DGMulti{NDIMS}; cells_per_dimension=nothing, + initial_refinement_level=nothing, + mapping=nothing, + coordinates_min=ntuple(_->-one(real(dg)), NDIMS), + coordinates_max=ntuple(_->one(real(dg)), NDIMS), + is_on_boundary=nothing, + periodicity=ntuple(_->false, NDIMS)) + +Keyword-based convenience constructor for `DGMultiMesh`. Matches the style of +`P4estMesh(cells_per_dimension; coordinates_min=..., coordinates_max=...)` for easy mesh-type swapping. + +Either `cells_per_dimension` or `initial_refinement_level` must be provided. When +`initial_refinement_level` is given, `cells_per_dimension` is set to `2^initial_refinement_level` +in each dimension, matching the `TreeMesh` convention. +""" +function DGMultiMesh(dg::DGMulti{NDIMS}; + cells_per_dimension = nothing, + initial_refinement_level = nothing, + mapping = nothing, + coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS), + coordinates_max = ntuple(_ -> one(real(dg)), NDIMS), + is_on_boundary = nothing, + periodicity = ntuple(_ -> false, NDIMS)) where {NDIMS} + @assert (cells_per_dimension !== nothing)!=(initial_refinement_level !== nothing) "Exactly one of cells_per_dimension or initial_refinement_level must be specified" + if initial_refinement_level !== nothing + cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) + end + if mapping !== nothing + return DGMultiMesh(dg, cells_per_dimension, mapping; + is_on_boundary = is_on_boundary, periodicity = periodicity) + else + return DGMultiMesh(dg, cells_per_dimension; + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + is_on_boundary = is_on_boundary, periodicity = periodicity) + end +end + """ DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension, mapping; is_on_boundary=nothing, diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index 5147ce29aa3..979d0920401 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -1022,6 +1022,25 @@ end # (e.g., from type instabilities) @test_allocations(Trixi.rhs!, semi, sol, 1000) end + +@testset "Unified mesh constructor signatures (P4estMesh)" begin + # 2D: keyword style (reference) — 4x4 trees + mesh_kw = P4estMesh((4, 4); polydeg = 1, + coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0)) + + # 2D: positional style + mesh_pos = P4estMesh((4, 4), (-1.0, -1.0), (1.0, 1.0); polydeg = 1) + @test size(mesh_kw.tree_node_coordinates) == size(mesh_pos.tree_node_coordinates) + @test mesh_kw.tree_node_coordinates ≈ mesh_pos.tree_node_coordinates + + # 2D: initial_refinement_level style — 1 tree refined 2 times = 4 cells per dimention. + # Internal layout differs (1 tree vs 16 trees), so only type and dimension are checked. + mesh_irl = P4estMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2, + polydeg = 1) + @test mesh_irl isa P4estMesh{2} + @test size(mesh_irl.tree_node_coordinates, ndims(mesh_irl) + 2) == 1 # 1 macro-tree +end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_unit.jl b/test/test_unit.jl index 819d2e8be8a..86b6fdad2ca 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -3526,4 +3526,69 @@ end end end +@testset "Unified mesh constructor signatures (StructuredMesh)" begin + # 1D: initial_refinement_level matches explicit cells_per_dimension (2^2 = 4) + mesh_1d_old = StructuredMesh((4,), (-1.0,), (1.0,)) + mesh_1d_new = StructuredMesh((-1.0,), (1.0,); initial_refinement_level = 2) + @test mesh_1d_old.cells_per_dimension == mesh_1d_new.cells_per_dimension + + # 1D: matches tuple form (like TreeMesh) + mesh_1d_scalar = StructuredMesh(-1.0, 1.0; initial_refinement_level = 2) + @test mesh_1d_scalar.cells_per_dimension == (4,) + + # 2D: initial_refinement_level + mesh_2d_old = StructuredMesh((4, 4), (-1.0, -1.0), (1.0, 1.0)) + mesh_2d_new = StructuredMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2) + @test mesh_2d_old.cells_per_dimension == mesh_2d_new.cells_per_dimension + + # 2D: keyword-based + mesh_2d_kw = StructuredMesh((4, 4); + coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0)) + @test mesh_2d_kw.cells_per_dimension == mesh_2d_old.cells_per_dimension + + # 3D: initial_refinement_level + mesh_3d_old = StructuredMesh((4, 4, 4), (-1.0, -1.0, -1.0), (1.0, 1.0, 1.0)) + mesh_3d_new = StructuredMesh((-1.0, -1.0, -1.0), (1.0, 1.0, 1.0); + initial_refinement_level = 2) + @test mesh_3d_old.cells_per_dimension == mesh_3d_new.cells_per_dimension + + # keyword-based constructor with mapping + mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) + mesh_mapping_pos = StructuredMesh((4, 4), mapping_2d) + mesh_mapping_kw = StructuredMesh((4, 4); mapping = mapping_2d) + @test mesh_mapping_pos.cells_per_dimension == mesh_mapping_kw.cells_per_dimension +end + +@testset "Unified mesh constructor signatures (DGMultiMesh)" begin + dg_1d = DGMulti(polydeg = 2, element_type = Line(), + approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_central), + volume_integral = VolumeIntegralFluxDifferencing(flux_central)) + + # 1D: initial_refinement_level (2^2 = 4 elements) matches positional cells_per_dimension + mesh_1d_old = DGMultiMesh(dg_1d, (4,)) + mesh_1d_new = DGMultiMesh(dg_1d; initial_refinement_level = 2) + @test mesh_1d_old.md.num_elements == mesh_1d_new.md.num_elements + + dg_2d = DGMulti(polydeg = 2, element_type = Quad(), + approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_central), + volume_integral = VolumeIntegralFluxDifferencing(flux_central)) + + # 2D: initial_refinement_level + mesh_2d_old = DGMultiMesh(dg_2d, (4, 4)) + mesh_2d_new = DGMultiMesh(dg_2d; initial_refinement_level = 2) + @test mesh_2d_old.md.num_elements == mesh_2d_new.md.num_elements + + # 2D: cells_per_dimension as Keyword + mesh_2d_kw = DGMultiMesh(dg_2d; cells_per_dimension = (4, 4)) + @test mesh_2d_kw.md.num_elements == mesh_2d_old.md.num_elements + + # error case: cells_per_dimension and initial_refinement_level simultaneously + @test_throws AssertionError DGMultiMesh(dg_2d; + cells_per_dimension = (4, 4), + initial_refinement_level = 2) +end + end #module From 59a6ae3cf032f0fd1573a7b2ea9a81a4820f6dc5 Mon Sep 17 00:00:00 2001 From: vincmarks Date: Wed, 6 May 2026 22:21:05 +0200 Subject: [PATCH 2/9] add more tests --- test/test_p4est_2d.jl | 17 ++++++++++++++++- test/test_t8code_2d.jl | 17 +++++++++++++++++ test/test_unit.jl | 14 ++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index 979d0920401..fe783ed33ba 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -1034,12 +1034,27 @@ end @test size(mesh_kw.tree_node_coordinates) == size(mesh_pos.tree_node_coordinates) @test mesh_kw.tree_node_coordinates ≈ mesh_pos.tree_node_coordinates - # 2D: initial_refinement_level style — 1 tree refined 2 times = 4 cells per dimention. + # 2D: initial_refinement_level style — 1 tree refined 2 times = 4 cells per dimension. # Internal layout differs (1 tree vs 16 trees), so only type and dimension are checked. mesh_irl = P4estMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2, polydeg = 1) @test mesh_irl isa P4estMesh{2} @test size(mesh_irl.tree_node_coordinates, ndims(mesh_irl) + 2) == 1 # 1 macro-tree + + # 2D: mapping + mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) + mesh_map = P4estMesh((4, 4), mapping_2d; polydeg = 1) + @test mesh_map isa P4estMesh{2} + @test size(mesh_map.tree_node_coordinates) == size(mesh_kw.tree_node_coordinates) + + # 2D: rectangle + f1(s) = SVector(-1.0, s) + f2(s) = SVector(1.0, s) + f3(s) = SVector(s, -1.0) + f4(s) = SVector(s, 1.0) + mesh_faces = P4estMesh((4, 4), (f1, f2, f3, f4); polydeg = 1) + @test mesh_faces isa P4estMesh{2} + @test size(mesh_faces.tree_node_coordinates) == size(mesh_kw.tree_node_coordinates) end end diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl index 8ed05ec4a22..aecd583e3c8 100644 --- a/test/test_t8code_2d.jl +++ b/test/test_t8code_2d.jl @@ -314,6 +314,23 @@ end @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) end + +@testset "Unified mesh constructor signatures (T8codeMesh)" begin + using Trixi: T8codeMesh + + # 2D: positional coordinates + mesh_pos = T8codeMesh((4, 4), (-1.0, -1.0), (1.0, 1.0)) + @test mesh_pos isa T8codeMesh{2} + + # 2D: mapping + mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) + mesh_map = T8codeMesh((4, 4), mapping_2d) + @test mesh_map isa T8codeMesh{2} + + # 2D: initial_refinement_level + mesh_irl = T8codeMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2) + @test mesh_irl isa T8codeMesh{2} +end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_unit.jl b/test/test_unit.jl index 86b6fdad2ca..10fb0a14021 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -3558,6 +3558,15 @@ end mesh_mapping_pos = StructuredMesh((4, 4), mapping_2d) mesh_mapping_kw = StructuredMesh((4, 4); mapping = mapping_2d) @test mesh_mapping_pos.cells_per_dimension == mesh_mapping_kw.cells_per_dimension + + # keyword-based constructor with faces (rectangle) + f1(s) = SVector(-1.0, s) + f2(s) = SVector(1.0, s) + f3(s) = SVector(s, -1.0) + f4(s) = SVector(s, 1.0) + mesh_faces_pos = StructuredMesh((4, 4), (f1, f2, f3, f4)) + mesh_faces_kw = StructuredMesh((4, 4); faces = (f1, f2, f3, f4)) + @test mesh_faces_pos.cells_per_dimension == mesh_faces_kw.cells_per_dimension end @testset "Unified mesh constructor signatures (DGMultiMesh)" begin @@ -3585,6 +3594,11 @@ end mesh_2d_kw = DGMultiMesh(dg_2d; cells_per_dimension = (4, 4)) @test mesh_2d_kw.md.num_elements == mesh_2d_old.md.num_elements + # 2D: mapping + mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) + mesh_2d_map = DGMultiMesh(dg_2d; cells_per_dimension = (4, 4), mapping = mapping_2d) + @test mesh_2d_map.md.num_elements == mesh_2d_old.md.num_elements + # error case: cells_per_dimension and initial_refinement_level simultaneously @test_throws AssertionError DGMultiMesh(dg_2d; cells_per_dimension = (4, 4), From 73c25a50df4a032cebf3309f77801ebd5010816e Mon Sep 17 00:00:00 2001 From: vincmarks Date: Tue, 12 May 2026 16:33:29 +0200 Subject: [PATCH 3/9] add keyword-only mesh constructor interface --- .../src/meshes/mesh_constructor_comparison.md | 104 ++++------------- .../elixir_advection_mesh_swap.jl | 46 ++------ src/meshes/p4est_mesh.jl | 107 ++---------------- src/meshes/structured_mesh.jl | 56 ++------- src/meshes/t8code_mesh.jl | 89 ++------------- src/meshes/tree_mesh.jl | 18 +++ src/solvers/dgmulti/types.jl | 42 +++---- test/test_p4est_2d.jl | 42 ++----- test/test_t8code_2d.jl | 20 ++-- test/test_unit.jl | 99 ++++++---------- 10 files changed, 146 insertions(+), 477 deletions(-) diff --git a/docs/src/meshes/mesh_constructor_comparison.md b/docs/src/meshes/mesh_constructor_comparison.md index 330c68895f0..29879655677 100644 --- a/docs/src/meshes/mesh_constructor_comparison.md +++ b/docs/src/meshes/mesh_constructor_comparison.md @@ -7,37 +7,32 @@ without having to rewrite the mesh construction call. An example demonstrating mesh-type swapping for the same simulation setup is provided in `examples/special_elixirs/elixir_advection_mesh_swap.jl`. -## Constructor overview +## Keyword-only interface -All structured mesh types support three equivalent styles for rectangular domains: - -### Style 1 — `cells_per_dimension` positional (classic) +All structured mesh types support a keyword-only constructor for rectangular domains +using `initial_refinement_level`. This directly mirrors the [`TreeMesh`](@ref) interface, +yielding `2^initial_refinement_level` cells per dimension: ```julia -StructuredMesh((16, 16), (-1.0, -1.0), (1.0, 1.0)) -P4estMesh((16, 16), (-1.0, -1.0), (1.0, 1.0); polydeg = 3) -T8codeMesh((16, 16), (-1.0, -1.0), (1.0, 1.0)) -DGMultiMesh(dg, (16, 16); - coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +# TreeMesh (reference call) +mesh = TreeMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 4, n_cells_max = 30_000) + +# Drop-in replacements. Only n_cells_max needs to be removed: +mesh = StructuredMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 4) +mesh = P4estMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 4) +mesh = T8codeMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 4) ``` -### Style 2 — `initial_refinement_level` (like `TreeMesh`) - -Directly mirrors the [`TreeMesh`](@ref) interface, yielding `2^initial_refinement_level` -cells per dimension: +[`DGMultiMesh`](@ref) also supports the same keyword arguments, but requires a solver `dg` +as the first positional argument: ```julia -# Original TreeMesh call -mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0); - initial_refinement_level = 4, - n_cells_max = 30_000) - -# Drop-in replacements — only n_cells_max needs to be removed: -mesh = StructuredMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 4) -mesh = P4estMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 4, polydeg = 3) -mesh = T8codeMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 4) -mesh = DGMultiMesh(dg; initial_refinement_level = 4, - coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) +mesh = DGMultiMesh(dg; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 4) ``` Note: for `StructuredMesh` and `DGMultiMesh`, `initial_refinement_level` directly sets @@ -45,41 +40,6 @@ Note: for `StructuredMesh` and `DGMultiMesh`, `initial_refinement_level` directl a single tree per dimension is created and refined `initial_refinement_level` times, which also yields `2^initial_refinement_level` leaf cells per dimension. -### Style 3 — keyword-based (like `P4estMesh`) - -All parameters as keywords, same style as the original [`P4estMesh`](@ref) interface: - -```julia -# Original P4estMesh keyword call -mesh = P4estMesh((16, 16); polydeg = 3, - coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) - -# Equivalent calls on other mesh types: -mesh = StructuredMesh((16, 16); - coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) -mesh = T8codeMesh((16, 16); - coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) -mesh = DGMultiMesh(dg; cells_per_dimension = (16, 16), - coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) -``` - -## Mapping and face-based constructors - -For curvilinear meshes, `mapping` and `faces` can also be passed positionally, -matching the [`StructuredMesh`](@ref) interface: - -```julia -# All three are equivalent: -StructuredMesh((16, 16), mapping) -P4estMesh((16, 16), mapping; polydeg = 3) -T8codeMesh((16, 16), mapping) - -# Face-based: -StructuredMesh((16, 16), faces) -P4estMesh((16, 16), faces; polydeg = 3) -T8codeMesh((16, 16), faces) -``` - ## Notes on `TreeMesh` [`TreeMesh`](@ref) has a fundamentally different design and cannot be used as a @@ -89,27 +49,7 @@ drop-in target in all cases: - It only supports refinement where all dimensions have the same number of cells (`2^level`). - It only supports rectangular domains — no `mapping` or `faces`. -When swapping **from** `TreeMesh` to another type, remove `n_cells_max` and -choose `initial_refinement_level` or compute `cells_per_dimension = (2^level, 2^level, ...)` manually. - -When swapping **to** `TreeMesh` from another type, the swap is only possible -if the domain is rectangular and `cells_per_dimension` is a power of two with the same value in all dimensions. - -## `trees_per_dimension` vs. `cells_per_dimension` - -In [`P4estMesh`](@ref) and [`T8codeMesh`](@ref), the first positional argument is named -`trees_per_dimension`, reflecting the p4est concept of a *forest of trees*: the argument -specifies how many trees exist per dimension, each of which may be further refined -by `initial_refinement_level`. The total leaf-cell count per dimension is therefore -`trees_per_dimension * 2^initial_refinement_level`. - -In contrast, [`StructuredMesh`](@ref) and [`DGMultiMesh`](@ref) use `cells_per_dimension` -for the final cell count with no further refinement. - -The Style 1 convenience constructors (`P4estMesh(cells_per_dimension, coordinates_min, ...)`) -pass `cells_per_dimension` directly as `trees_per_dimension` with `initial_refinement_level = 0` -by default, so `cells_per_dimension` equals the actual leaf-cell count per dimension. +When swapping **from** `TreeMesh` to another type, remove `n_cells_max`. +When swapping **to** `TreeMesh` from another type, add `n_cells_max` and ensure +the domain is rectangular. -The Style 2 convenience constructors (`P4estMesh(coordinates_min, coordinates_max; initial_refinement_level=...)`) -use `trees_per_dimension = (1, 1, ...)` internally and forward `initial_refinement_level`, -so the leaf-cell count per dimension is `2^initial_refinement_level` — consistent with `TreeMesh`. diff --git a/examples/special_elixirs/elixir_advection_mesh_swap.jl b/examples/special_elixirs/elixir_advection_mesh_swap.jl index 461d50c7981..78ea792e14d 100644 --- a/examples/special_elixirs/elixir_advection_mesh_swap.jl +++ b/examples/special_elixirs/elixir_advection_mesh_swap.jl @@ -1,6 +1,6 @@ # Demonstrates the unified mesh constructor interface for rectangular domains. -# The same 2D linear advection setup is run with different mesh types and -# constructor styles using equivalent calls. +# The same 2D linear advection setup is run with different mesh types using +# the keyword-only constructor style with `initial_refinement_level`. # # See docs/src/meshes/mesh_constructor_comparison.md for more details @@ -39,49 +39,23 @@ function run_advection(mesh) end ############################################################################### -# initial_refinement_level (like TreeMesh) +# Keyword-only interface for mesh constructors -# Original TreeMesh call (for reference): -mesh = TreeMesh(coordinates_min, coordinates_max; +mesh = TreeMesh(coordinates_min = coordinates_min, + coordinates_max = coordinates_max, initial_refinement_level = initial_refinement_level, n_cells_max = 30_000, periodicity = true) sol = run_advection(mesh) -# Drop-in replacements — only n_cells_max needs to be removed: -mesh = StructuredMesh(coordinates_min, coordinates_max; - initial_refinement_level = initial_refinement_level, - periodicity = true) -sol = run_advection(mesh) - -# polydeg here controls the geometry interpolation degree of the mesh -mesh = P4estMesh(coordinates_min, coordinates_max; - initial_refinement_level = initial_refinement_level, - polydeg = 1, periodicity = true) -sol = run_advection(mesh) - -############################################################################### -# cells_per_dimension positional - -cpd = ntuple(_ -> 2^initial_refinement_level, 2) # (16, 16) - -mesh = StructuredMesh(cpd, coordinates_min, coordinates_max; periodicity = true) -sol = run_advection(mesh) - -mesh = P4estMesh(cpd, coordinates_min, coordinates_max; polydeg = 1, periodicity = true) -sol = run_advection(mesh) - -############################################################################### -# keyword-based - -mesh = StructuredMesh(cpd; - coordinates_min = coordinates_min, +mesh = StructuredMesh(coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = initial_refinement_level, periodicity = true) sol = run_advection(mesh) -mesh = P4estMesh(cpd; - polydeg = 1, - coordinates_min = coordinates_min, +# polydeg = 1 at default for P4estMesh +mesh = P4estMesh(coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = initial_refinement_level, periodicity = true) sol = run_advection(mesh) diff --git a/src/meshes/p4est_mesh.jl b/src/meshes/p4est_mesh.jl index 34ed4fae8c5..a37f04e1cb6 100644 --- a/src/meshes/p4est_mesh.jl +++ b/src/meshes/p4est_mesh.jl @@ -245,95 +245,25 @@ function P4estMesh(trees_per_dimension; polydeg, p4est_partition_allow_for_coarsening) end -# Convenience constructors matching the positional interface of StructuredMesh - -""" - P4estMesh(cells_per_dimension, coordinates_min, coordinates_max; polydeg, kwargs...) - -Convenience constructor for a rectangular `P4estMesh`. Matches the positional interface of -`StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max)` for easy mesh-type swapping. -""" -function P4estMesh(cells_per_dimension, coordinates_min, coordinates_max; - polydeg, - RealT = Float64, - initial_refinement_level = 0, - periodicity = false, - unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) - return P4estMesh(cells_per_dimension; - polydeg = polydeg, - coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - RealT = RealT, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity, - unsaved_changes = unsaved_changes, - p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) -end - -""" - P4estMesh(cells_per_dimension, mapping::Function; polydeg, kwargs...) - -Convenience constructor for a curved `P4estMesh`. Matches the positional interface of -`StructuredMesh(cells_per_dimension, mapping)` for easy mesh-type swapping. -""" -function P4estMesh(cells_per_dimension, mapping::Function; - polydeg, - RealT = Float64, - initial_refinement_level = 0, - periodicity = false, - unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) - return P4estMesh(cells_per_dimension; - polydeg = polydeg, - mapping = mapping, - RealT = RealT, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity, - unsaved_changes = unsaved_changes, - p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) -end - -""" - P4estMesh(cells_per_dimension, faces::Tuple; polydeg, kwargs...) - -Convenience constructor for a face-parametrized `P4estMesh`. Matches the positional interface of -`StructuredMesh(cells_per_dimension, faces)` for easy mesh-type swapping. """ -function P4estMesh(cells_per_dimension, faces::Tuple; - polydeg, - RealT = Float64, - initial_refinement_level = 0, - periodicity = false, - unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) - return P4estMesh(cells_per_dimension; - polydeg = polydeg, - faces = faces, - RealT = RealT, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity, - unsaved_changes = unsaved_changes, - p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) -end + P4estMesh(; coordinates_min, coordinates_max, initial_refinement_level, polydeg=1, kwargs...) -# TreeMesh-compatible constructors: accept (coordinates_min, coordinates_max; initial_refinement_level) +Create a rectangular `P4estMesh` using keyword arguments only, for easy mesh-type swapping +with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`T8codeMesh`](@ref), and +[`DGMultiMesh`](@ref). -""" - P4estMesh(coordinates_min, coordinates_max; initial_refinement_level, polydeg, kwargs...) - -Create a rectangular `P4estMesh` from `coordinates_min`/`coordinates_max` and -`initial_refinement_level`, using the same interface as `TreeMesh` for easy mesh-type swapping. -Creates a single tree per dimension that is uniformly refined `initial_refinement_level` times, +A single tree per dimension is created and uniformly refined `initial_refinement_level` times, yielding `2^initial_refinement_level` cells per dimension. """ -function P4estMesh(coordinates_min::NTuple{NDIMS}, coordinates_max::NTuple{NDIMS}; +function P4estMesh(; coordinates_min, + coordinates_max, initial_refinement_level, - polydeg, + polydeg = 1, RealT = Float64, periodicity = false, unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) where {NDIMS} + p4est_partition_allow_for_coarsening = true) + NDIMS = length(coordinates_min) return P4estMesh(ntuple(_ -> 1, NDIMS); polydeg = polydeg, coordinates_min = coordinates_min, @@ -345,23 +275,6 @@ function P4estMesh(coordinates_min::NTuple{NDIMS}, coordinates_max::NTuple{NDIMS p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) end -# 1D convenience -function P4estMesh(coordinates_min::Real, coordinates_max::Real; - initial_refinement_level, - polydeg, - RealT = Float64, - periodicity = false, - unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) - return P4estMesh((coordinates_min,), (coordinates_max,); - initial_refinement_level = initial_refinement_level, - polydeg = polydeg, - RealT = RealT, - periodicity = periodicity, - unsaved_changes = unsaved_changes, - p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) -end - # 2D version function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{2}, periodicity) diff --git a/src/meshes/structured_mesh.jl b/src/meshes/structured_mesh.jl index 09509c96458..4e2e2ba4604 100644 --- a/src/meshes/structured_mesh.jl +++ b/src/meshes/structured_mesh.jl @@ -146,63 +146,23 @@ function StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; end """ - StructuredMesh(coordinates_min, coordinates_max; initial_refinement_level, periodicity=false) + StructuredMesh(; coordinates_min, coordinates_max, initial_refinement_level, periodicity=false) + +Create a rectangular `StructuredMesh` using keyword arguments only, for easy mesh-type swapping +with [`TreeMesh`](@ref), [`P4estMesh`](@ref), [`T8codeMesh`](@ref), and [`DGMultiMesh`](@ref). -Create a rectangular `StructuredMesh` from `coordinates_min`/`coordinates_max` and -`initial_refinement_level`, using the same interface as `TreeMesh` for easy mesh-type swapping. The number of cells per dimension is `2^initial_refinement_level`. """ -function StructuredMesh(coordinates_min::NTuple{NDIMS}, - coordinates_max::NTuple{NDIMS}; +function StructuredMesh(; coordinates_min, + coordinates_max, initial_refinement_level, - periodicity = false) where {NDIMS} + periodicity = false) + NDIMS = length(coordinates_min) cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) return StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; periodicity = periodicity) end -# 1D convenience -function StructuredMesh(coordinates_min::Real, coordinates_max::Real; - initial_refinement_level, - periodicity = false) - return StructuredMesh((coordinates_min,), (coordinates_max,); - initial_refinement_level = initial_refinement_level, - periodicity = periodicity) -end - -""" - StructuredMesh(cells_per_dimension; mapping=nothing, faces=nothing, - coordinates_min=nothing, coordinates_max=nothing, - RealT=Float64, periodicity=false) - -Convenience constructor for `StructuredMesh` using a keyword-based interface, matching the style -of `P4estMesh(cells_per_dimension; ...)` for easy mesh-type swapping. - -Exactly one of `mapping`, `faces`, or `coordinates_min`/`coordinates_max` must be specified. -""" -function StructuredMesh(cells_per_dimension; - mapping = nothing, - faces = nothing, - coordinates_min = nothing, - coordinates_max = nothing, - RealT = Float64, - periodicity = false) - @assert count(i -> i !== nothing, - (mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified" - @assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified" - - if faces !== nothing - return StructuredMesh(cells_per_dimension, faces; RealT = RealT, - periodicity = periodicity) - elseif coordinates_min !== nothing - return StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; - periodicity = periodicity) - else - return StructuredMesh(cells_per_dimension, mapping; RealT = RealT, - periodicity = periodicity) - end -end - # Extract a string of the code that defines the mapping function function mapping2string(mapping, ndims, RealT = Float64) return string(code_string(mapping, ntuple(_ -> RealT, ndims))) diff --git a/src/meshes/t8code_mesh.jl b/src/meshes/t8code_mesh.jl index 52c34e4467f..1db55d2149d 100644 --- a/src/meshes/t8code_mesh.jl +++ b/src/meshes/t8code_mesh.jl @@ -505,81 +505,23 @@ function T8codeMesh(trees_per_dimension; polydeg = 1, mapping = mapping_) end -# Convenience constructors matching the positional interface of StructuredMesh - """ - T8codeMesh(cells_per_dimension, coordinates_min, coordinates_max; polydeg=1, kwargs...) + T8codeMesh(; coordinates_min, coordinates_max, initial_refinement_level, polydeg=1, kwargs...) -Convenience constructor for a rectangular `T8codeMesh`. Matches the positional interface of -`StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max)` for easy mesh-type swapping. -""" -function T8codeMesh(cells_per_dimension, coordinates_min, coordinates_max; - polydeg = 1, - RealT = Float64, - initial_refinement_level = 0, - periodicity = false) - return T8codeMesh(cells_per_dimension; - polydeg = polydeg, - coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - RealT = RealT, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity) -end +Create a rectangular `T8codeMesh` using keyword arguments only, for easy mesh-type swapping +with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and +[`DGMultiMesh`](@ref). -""" - T8codeMesh(cells_per_dimension, mapping::Function; polydeg=1, kwargs...) - -Convenience constructor for a curved `T8codeMesh`. Matches the positional interface of -`StructuredMesh(cells_per_dimension, mapping)` for easy mesh-type swapping. -""" -function T8codeMesh(cells_per_dimension, mapping::Function; - polydeg = 1, - RealT = Float64, - initial_refinement_level = 0, - periodicity = false) - return T8codeMesh(cells_per_dimension; - polydeg = polydeg, - mapping = mapping, - RealT = RealT, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity) -end - -""" - T8codeMesh(cells_per_dimension, faces::Tuple; polydeg=1, kwargs...) - -Convenience constructor for a face-parametrized `T8codeMesh`. Matches the positional interface of -`StructuredMesh(cells_per_dimension, faces)` for easy mesh-type swapping. -""" -function T8codeMesh(cells_per_dimension, faces::Tuple; - polydeg = 1, - RealT = Float64, - initial_refinement_level = 0, - periodicity = false) - return T8codeMesh(cells_per_dimension; - polydeg = polydeg, - faces = faces, - RealT = RealT, - initial_refinement_level = initial_refinement_level, - periodicity = periodicity) -end - -# TreeMesh-compatible constructors: accept (coordinates_min, coordinates_max; initial_refinement_level) - -""" - T8codeMesh(coordinates_min, coordinates_max; initial_refinement_level, polydeg=1, kwargs...) - -Create a rectangular `T8codeMesh` from `coordinates_min`/`coordinates_max` and -`initial_refinement_level`, using the same interface as `TreeMesh` for easy mesh-type swapping. -Creates a single tree per dimension that is uniformly refined `initial_refinement_level` times, +A single tree per dimension is created and uniformly refined `initial_refinement_level` times, yielding `2^initial_refinement_level` cells per dimension. """ -function T8codeMesh(coordinates_min::NTuple{NDIMS}, coordinates_max::NTuple{NDIMS}; +function T8codeMesh(; coordinates_min, + coordinates_max, initial_refinement_level, polydeg = 1, RealT = Float64, - periodicity = false) where {NDIMS} + periodicity = false) + NDIMS = length(coordinates_min) return T8codeMesh(ntuple(_ -> 1, NDIMS); polydeg = polydeg, coordinates_min = coordinates_min, @@ -589,19 +531,6 @@ function T8codeMesh(coordinates_min::NTuple{NDIMS}, coordinates_max::NTuple{NDIM periodicity = periodicity) end -# 1D convenience -function T8codeMesh(coordinates_min::Real, coordinates_max::Real; - initial_refinement_level, - polydeg = 1, - RealT = Float64, - periodicity = false) - return T8codeMesh((coordinates_min,), (coordinates_max,); - initial_refinement_level = initial_refinement_level, - polydeg = polydeg, - RealT = RealT, - periodicity = periodicity) -end - """ T8codeMesh(cmesh::Ptr{t8_cmesh}, mapping=nothing, polydeg=1, RealT=Float64, diff --git a/src/meshes/tree_mesh.jl b/src/meshes/tree_mesh.jl index 4e452b48c44..1cea806954b 100644 --- a/src/meshes/tree_mesh.jl +++ b/src/meshes/tree_mesh.jl @@ -225,6 +225,24 @@ function TreeMesh(coordinates_min::Real, coordinates_max::Real; return TreeMesh((coordinates_min,), (coordinates_max,); kwargs...) end +""" + TreeMesh(; coordinates_min, coordinates_max, initial_refinement_level, n_cells_max, kwargs...) + +Create a [`TreeMesh`](@ref) using keyword arguments only, for easy mesh-type swapping +with [`StructuredMesh`](@ref), [`P4estMesh`](@ref), [`T8codeMesh`](@ref), and +[`DGMultiMesh`](@ref). +""" +function TreeMesh(; coordinates_min, + coordinates_max, + initial_refinement_level, + n_cells_max, + kwargs...) + return TreeMesh(coordinates_min, coordinates_max; + initial_refinement_level = initial_refinement_level, + n_cells_max = n_cells_max, + kwargs...) +end + function Base.show(io::IO, mesh::TreeMesh{NDIMS, TreeType}) where {NDIMS, TreeType} print(io, "TreeMesh{", NDIMS, ", ", TreeType, "} with length ", mesh.tree.length) return nothing diff --git a/src/solvers/dgmulti/types.jl b/src/solvers/dgmulti/types.jl index bd06652587a..3441c672db6 100644 --- a/src/solvers/dgmulti/types.jl +++ b/src/solvers/dgmulti/types.jl @@ -299,42 +299,28 @@ function DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension; end """ - DGMultiMesh(dg::DGMulti{NDIMS}; cells_per_dimension=nothing, - initial_refinement_level=nothing, - mapping=nothing, - coordinates_min=ntuple(_->-one(real(dg)), NDIMS), - coordinates_max=ntuple(_->one(real(dg)), NDIMS), + DGMultiMesh(dg::DGMulti{NDIMS}; coordinates_min, coordinates_max, + initial_refinement_level, is_on_boundary=nothing, periodicity=ntuple(_->false, NDIMS)) -Keyword-based convenience constructor for `DGMultiMesh`. Matches the style of -`P4estMesh(cells_per_dimension; coordinates_min=..., coordinates_max=...)` for easy mesh-type swapping. +Create a rectangular `DGMultiMesh` using keyword arguments only, for easy mesh-type swapping +with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and +[`T8codeMesh`](@ref). -Either `cells_per_dimension` or `initial_refinement_level` must be provided. When -`initial_refinement_level` is given, `cells_per_dimension` is set to `2^initial_refinement_level` -in each dimension, matching the `TreeMesh` convention. +The number of cells per dimension is `2^initial_refinement_level`. """ function DGMultiMesh(dg::DGMulti{NDIMS}; - cells_per_dimension = nothing, - initial_refinement_level = nothing, - mapping = nothing, - coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS), - coordinates_max = ntuple(_ -> one(real(dg)), NDIMS), + coordinates_min, + coordinates_max, + initial_refinement_level, is_on_boundary = nothing, periodicity = ntuple(_ -> false, NDIMS)) where {NDIMS} - @assert (cells_per_dimension !== nothing)!=(initial_refinement_level !== nothing) "Exactly one of cells_per_dimension or initial_refinement_level must be specified" - if initial_refinement_level !== nothing - cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) - end - if mapping !== nothing - return DGMultiMesh(dg, cells_per_dimension, mapping; - is_on_boundary = is_on_boundary, periodicity = periodicity) - else - return DGMultiMesh(dg, cells_per_dimension; - coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - is_on_boundary = is_on_boundary, periodicity = periodicity) - end + cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) + return DGMultiMesh(dg, cells_per_dimension; + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + is_on_boundary = is_on_boundary, periodicity = periodicity) end """ diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index fe783ed33ba..34bb00d7610 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -1024,37 +1024,17 @@ end end @testset "Unified mesh constructor signatures (P4estMesh)" begin - # 2D: keyword style (reference) — 4x4 trees - mesh_kw = P4estMesh((4, 4); polydeg = 1, - coordinates_min = (-1.0, -1.0), - coordinates_max = (1.0, 1.0)) - - # 2D: positional style - mesh_pos = P4estMesh((4, 4), (-1.0, -1.0), (1.0, 1.0); polydeg = 1) - @test size(mesh_kw.tree_node_coordinates) == size(mesh_pos.tree_node_coordinates) - @test mesh_kw.tree_node_coordinates ≈ mesh_pos.tree_node_coordinates - - # 2D: initial_refinement_level style — 1 tree refined 2 times = 4 cells per dimension. - # Internal layout differs (1 tree vs 16 trees), so only type and dimension are checked. - mesh_irl = P4estMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2, - polydeg = 1) - @test mesh_irl isa P4estMesh{2} - @test size(mesh_irl.tree_node_coordinates, ndims(mesh_irl) + 2) == 1 # 1 macro-tree - - # 2D: mapping - mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) - mesh_map = P4estMesh((4, 4), mapping_2d; polydeg = 1) - @test mesh_map isa P4estMesh{2} - @test size(mesh_map.tree_node_coordinates) == size(mesh_kw.tree_node_coordinates) - - # 2D: rectangle - f1(s) = SVector(-1.0, s) - f2(s) = SVector(1.0, s) - f3(s) = SVector(s, -1.0) - f4(s) = SVector(s, 1.0) - mesh_faces = P4estMesh((4, 4), (f1, f2, f3, f4); polydeg = 1) - @test mesh_faces isa P4estMesh{2} - @test size(mesh_faces.tree_node_coordinates) == size(mesh_kw.tree_node_coordinates) + # 2D: reference (trees_per_dimension) positional + mesh_ref = P4estMesh((4, 4); polydeg = 1, + coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0)) + + # 2D: using initial_refinement_level + # polydeg defaults to 1 + mesh_kw = P4estMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 2) + @test mesh_kw isa P4estMesh{2} + @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 # 1 macro-tree end end diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl index aecd583e3c8..31ee70fec12 100644 --- a/test/test_t8code_2d.jl +++ b/test/test_t8code_2d.jl @@ -317,19 +317,13 @@ end @testset "Unified mesh constructor signatures (T8codeMesh)" begin using Trixi: T8codeMesh - - # 2D: positional coordinates - mesh_pos = T8codeMesh((4, 4), (-1.0, -1.0), (1.0, 1.0)) - @test mesh_pos isa T8codeMesh{2} - - # 2D: mapping - mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) - mesh_map = T8codeMesh((4, 4), mapping_2d) - @test mesh_map isa T8codeMesh{2} - - # 2D: initial_refinement_level - mesh_irl = T8codeMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2) - @test mesh_irl isa T8codeMesh{2} + # polydeg = 1 at default for T8codeMesh + mesh_ref = T8codeMesh((4, 4); + coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) + mesh_kw = T8codeMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + initial_refinement_level = 2) + @test mesh_kw isa T8codeMesh{2} + @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 end end diff --git a/test/test_unit.jl b/test/test_unit.jl index 10fb0a14021..3af5db09ff0 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -93,6 +93,14 @@ end initial_refinement_level = 2, n_cells_max = 10_000, periodicity = true) + + # Keyword-only constructor + mesh_ref = TreeMesh((-1.0, -1.0), (1.0, 1.0); + initial_refinement_level = 2, n_cells_max = 10_000) + mesh_kw = TreeMesh(; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0), + initial_refinement_level = 2, n_cells_max = 10_000) + @test Trixi.ncells(mesh_kw) == Trixi.ncells(mesh_ref) end @testset "helper functions" begin @@ -3527,46 +3535,25 @@ end end @testset "Unified mesh constructor signatures (StructuredMesh)" begin - # 1D: initial_refinement_level matches explicit cells_per_dimension (2^2 = 4) - mesh_1d_old = StructuredMesh((4,), (-1.0,), (1.0,)) - mesh_1d_new = StructuredMesh((-1.0,), (1.0,); initial_refinement_level = 2) - @test mesh_1d_old.cells_per_dimension == mesh_1d_new.cells_per_dimension - - # 1D: matches tuple form (like TreeMesh) - mesh_1d_scalar = StructuredMesh(-1.0, 1.0; initial_refinement_level = 2) - @test mesh_1d_scalar.cells_per_dimension == (4,) - - # 2D: initial_refinement_level - mesh_2d_old = StructuredMesh((4, 4), (-1.0, -1.0), (1.0, 1.0)) - mesh_2d_new = StructuredMesh((-1.0, -1.0), (1.0, 1.0); initial_refinement_level = 2) - @test mesh_2d_old.cells_per_dimension == mesh_2d_new.cells_per_dimension - - # 2D: keyword-based - mesh_2d_kw = StructuredMesh((4, 4); - coordinates_min = (-1.0, -1.0), - coordinates_max = (1.0, 1.0)) - @test mesh_2d_kw.cells_per_dimension == mesh_2d_old.cells_per_dimension - - # 3D: initial_refinement_level - mesh_3d_old = StructuredMesh((4, 4, 4), (-1.0, -1.0, -1.0), (1.0, 1.0, 1.0)) - mesh_3d_new = StructuredMesh((-1.0, -1.0, -1.0), (1.0, 1.0, 1.0); - initial_refinement_level = 2) - @test mesh_3d_old.cells_per_dimension == mesh_3d_new.cells_per_dimension - - # keyword-based constructor with mapping - mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) - mesh_mapping_pos = StructuredMesh((4, 4), mapping_2d) - mesh_mapping_kw = StructuredMesh((4, 4); mapping = mapping_2d) - @test mesh_mapping_pos.cells_per_dimension == mesh_mapping_kw.cells_per_dimension - - # keyword-based constructor with faces (rectangle) - f1(s) = SVector(-1.0, s) - f2(s) = SVector(1.0, s) - f3(s) = SVector(s, -1.0) - f4(s) = SVector(s, 1.0) - mesh_faces_pos = StructuredMesh((4, 4), (f1, f2, f3, f4)) - mesh_faces_kw = StructuredMesh((4, 4); faces = (f1, f2, f3, f4)) - @test mesh_faces_pos.cells_per_dimension == mesh_faces_kw.cells_per_dimension + # 1D: keyword interface (2^2 = 4 cells per dimension) + mesh_1d_ref = StructuredMesh((4,), (-1.0,), (1.0,)) + mesh_1d_kw = StructuredMesh(; coordinates_min = (-1.0,), coordinates_max = (1.0,), + initial_refinement_level = 2) + @test mesh_1d_ref.cells_per_dimension == mesh_1d_kw.cells_per_dimension + + # 2D: keyword interface + mesh_2d_ref = StructuredMesh((4, 4), (-1.0, -1.0), (1.0, 1.0)) + mesh_2d_kw = StructuredMesh(; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0), + initial_refinement_level = 2) + @test mesh_2d_ref.cells_per_dimension == mesh_2d_kw.cells_per_dimension + + # 3D: keyword interface + mesh_3d_ref = StructuredMesh((4, 4, 4), (-1.0, -1.0, -1.0), (1.0, 1.0, 1.0)) + mesh_3d_kw = StructuredMesh(; coordinates_min = (-1.0, -1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + initial_refinement_level = 2) + @test mesh_3d_ref.cells_per_dimension == mesh_3d_kw.cells_per_dimension end @testset "Unified mesh constructor signatures (DGMultiMesh)" begin @@ -3575,34 +3562,22 @@ end surface_integral = SurfaceIntegralWeakForm(flux_central), volume_integral = VolumeIntegralFluxDifferencing(flux_central)) - # 1D: initial_refinement_level (2^2 = 4 elements) matches positional cells_per_dimension - mesh_1d_old = DGMultiMesh(dg_1d, (4,)) - mesh_1d_new = DGMultiMesh(dg_1d; initial_refinement_level = 2) - @test mesh_1d_old.md.num_elements == mesh_1d_new.md.num_elements + # 1D: keyword interface (2^2 = 4 elements) + mesh_1d_ref = DGMultiMesh(dg_1d, (4,)) + mesh_1d_kw = DGMultiMesh(dg_1d; coordinates_min = (-1.0,), coordinates_max = (1.0,), + initial_refinement_level = 2) + @test mesh_1d_ref.md.num_elements == mesh_1d_kw.md.num_elements dg_2d = DGMulti(polydeg = 2, element_type = Quad(), approximation_type = Polynomial(), surface_integral = SurfaceIntegralWeakForm(flux_central), volume_integral = VolumeIntegralFluxDifferencing(flux_central)) - # 2D: initial_refinement_level - mesh_2d_old = DGMultiMesh(dg_2d, (4, 4)) - mesh_2d_new = DGMultiMesh(dg_2d; initial_refinement_level = 2) - @test mesh_2d_old.md.num_elements == mesh_2d_new.md.num_elements - - # 2D: cells_per_dimension as Keyword - mesh_2d_kw = DGMultiMesh(dg_2d; cells_per_dimension = (4, 4)) - @test mesh_2d_kw.md.num_elements == mesh_2d_old.md.num_elements - - # 2D: mapping - mapping_2d = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)) - mesh_2d_map = DGMultiMesh(dg_2d; cells_per_dimension = (4, 4), mapping = mapping_2d) - @test mesh_2d_map.md.num_elements == mesh_2d_old.md.num_elements - - # error case: cells_per_dimension and initial_refinement_level simultaneously - @test_throws AssertionError DGMultiMesh(dg_2d; - cells_per_dimension = (4, 4), - initial_refinement_level = 2) + # 2D: keyword interface + mesh_2d_ref = DGMultiMesh(dg_2d, (4, 4)) + mesh_2d_kw = DGMultiMesh(dg_2d; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0), initial_refinement_level = 2) + @test mesh_2d_ref.md.num_elements == mesh_2d_kw.md.num_elements end end #module From 083c3b696195b2d190530261c3c6ac42aeff48df Mon Sep 17 00:00:00 2001 From: vincmarks Date: Wed, 13 May 2026 11:23:05 +0200 Subject: [PATCH 4/9] add test for elixir_advection_mesh_swap.jl --- test/test_special_elixirs.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_special_elixirs.jl b/test/test_special_elixirs.jl index 5aadf1b3c83..146efd016f7 100644 --- a/test/test_special_elixirs.jl +++ b/test/test_special_elixirs.jl @@ -493,6 +493,11 @@ end @test_trixi_include(joinpath(examples_dir(), "special_elixirs", "elixir_euler_ad.jl")) end + + @timed_testset "elixir_advection_mesh_swap.jl" begin + @test_trixi_include(joinpath(examples_dir(), "special_elixirs", + "elixir_advection_mesh_swap.jl")) + end end end From ad7613822041fcb8db73bda2be6c53605ce9e363 Mon Sep 17 00:00:00 2001 From: vincmarks Date: Mon, 18 May 2026 16:49:22 +0200 Subject: [PATCH 5/9] rename to refinement_level, document kwargs, add length checks, update docs --- .../src/meshes/mesh_constructor_comparison.md | 44 +++++++------ .../elixir_advection_mesh_swap.jl | 61 ------------------- src/meshes/p4est_mesh.jl | 31 +++++++--- src/meshes/structured_mesh.jl | 24 ++++++-- src/meshes/t8code_mesh.jl | 16 ++--- src/meshes/tree_mesh.jl | 12 ++-- src/solvers/dgmulti/types.jl | 11 ++-- test/test_p4est_2d.jl | 5 +- test/test_special_elixirs.jl | 5 -- test/test_unit.jl | 12 ++-- 10 files changed, 98 insertions(+), 123 deletions(-) delete mode 100644 examples/special_elixirs/elixir_advection_mesh_swap.jl diff --git a/docs/src/meshes/mesh_constructor_comparison.md b/docs/src/meshes/mesh_constructor_comparison.md index 29879655677..0d31c0c10c6 100644 --- a/docs/src/meshes/mesh_constructor_comparison.md +++ b/docs/src/meshes/mesh_constructor_comparison.md @@ -4,45 +4,50 @@ Trixi.jl provides several mesh types suited for different scenarios. It may be useful to quickly swap between mesh types without having to rewrite the mesh construction call. -An example demonstrating mesh-type swapping for the same simulation setup is -provided in `examples/special_elixirs/elixir_advection_mesh_swap.jl`. - ## Keyword-only interface All structured mesh types support a keyword-only constructor for rectangular domains -using `initial_refinement_level`. This directly mirrors the [`TreeMesh`](@ref) interface, -yielding `2^initial_refinement_level` cells per dimension: +using `refinement_level`, yielding `2^refinement_level` cells per dimension. + +```@example mesh-swap +using Trixi -```julia -# TreeMesh (reference call) mesh = TreeMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 4, n_cells_max = 30_000) + refinement_level = 2, n_cells_max = 30_000) +``` -# Drop-in replacements. Only n_cells_max needs to be removed: +```@example mesh-swap mesh = StructuredMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 4) + refinement_level = 2) +``` + +```@example mesh-swap mesh = P4estMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 4) + refinement_level = 2) +``` + +```@example mesh-swap mesh = T8codeMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 4) + refinement_level = 2) ``` [`DGMultiMesh`](@ref) also supports the same keyword arguments, but requires a solver `dg` as the first positional argument: -```julia +```@example mesh-swap +dg = DGMulti(polydeg = 1, element_type = Quad()) mesh = DGMultiMesh(dg; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 4) + refinement_level = 2) ``` -Note: for `StructuredMesh` and `DGMultiMesh`, `initial_refinement_level` directly sets -`cells_per_dimension = 2^initial_refinement_level`. For `P4estMesh` and `T8codeMesh`, -a single tree per dimension is created and refined `initial_refinement_level` times, -which also yields `2^initial_refinement_level` leaf cells per dimension. +Note: for `StructuredMesh` and `DGMultiMesh`, `refinement_level` directly sets +`cells_per_dimension = 2^refinement_level`. For `P4estMesh` and `T8codeMesh`, +a single tree per dimension is created and refined `refinement_level` times, +which also yields `2^refinement_level` leaf cells per dimension. ## Notes on `TreeMesh` -[`TreeMesh`](@ref) has a fundamentally different design and cannot be used as a +[`TreeMesh`](@ref) has a different design and cannot be used as a drop-in target in all cases: - It requires `n_cells_max` with no equivalent in other mesh types. @@ -52,4 +57,3 @@ drop-in target in all cases: When swapping **from** `TreeMesh` to another type, remove `n_cells_max`. When swapping **to** `TreeMesh` from another type, add `n_cells_max` and ensure the domain is rectangular. - diff --git a/examples/special_elixirs/elixir_advection_mesh_swap.jl b/examples/special_elixirs/elixir_advection_mesh_swap.jl deleted file mode 100644 index 78ea792e14d..00000000000 --- a/examples/special_elixirs/elixir_advection_mesh_swap.jl +++ /dev/null @@ -1,61 +0,0 @@ -# Demonstrates the unified mesh constructor interface for rectangular domains. -# The same 2D linear advection setup is run with different mesh types using -# the keyword-only constructor style with `initial_refinement_level`. -# -# See docs/src/meshes/mesh_constructor_comparison.md for more details - -using OrdinaryDiffEqLowStorageRK -using Trixi - -############################################################################### -# Parameters - -advection_velocity = (0.2, -0.7) -equations = LinearScalarAdvectionEquation2D(advection_velocity) - -# The solution polynomial degree here is only used by the solver and independent of the mesh geometry -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) - -coordinates_min = (-1.0, -1.0) -coordinates_max = (1.0, 1.0) -initial_refinement_level = 4 # 2^4 = 16 cells per dimension - -t_end = 1.0 - -############################################################################### -# Helper function: build semi and solve for a mesh -# Based on: examples/tree_2d_dgsem/elixir_advection_basic.jl - -function run_advection(mesh) - semi = SemidiscretizationHyperbolic(mesh, equations, - initial_condition_convergence_test, solver; - boundary_conditions = boundary_condition_periodic) - ode = semidiscretize(semi, (0.0, t_end)) - stepsize_callback = StepsizeCallback(cfl = 1.6) - sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false); - dt = 1.0, ode_default_options()..., - callback = CallbackSet(stepsize_callback)) - return sol -end - -############################################################################### -# Keyword-only interface for mesh constructors - -mesh = TreeMesh(coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - initial_refinement_level = initial_refinement_level, - n_cells_max = 30_000, periodicity = true) -sol = run_advection(mesh) - -mesh = StructuredMesh(coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - initial_refinement_level = initial_refinement_level, - periodicity = true) -sol = run_advection(mesh) - -# polydeg = 1 at default for P4estMesh -mesh = P4estMesh(coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - initial_refinement_level = initial_refinement_level, - periodicity = true) -sol = run_advection(mesh) diff --git a/src/meshes/p4est_mesh.jl b/src/meshes/p4est_mesh.jl index a37f04e1cb6..f5ae6f8be37 100644 --- a/src/meshes/p4est_mesh.jl +++ b/src/meshes/p4est_mesh.jl @@ -246,30 +246,47 @@ function P4estMesh(trees_per_dimension; polydeg, end """ - P4estMesh(; coordinates_min, coordinates_max, initial_refinement_level, polydeg=1, kwargs...) + P4estMesh(; coordinates_min, coordinates_max, refinement_level, polydeg=1, kwargs...) Create a rectangular `P4estMesh` using keyword arguments only, for easy mesh-type swapping -with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`T8codeMesh`](@ref), and -[`DGMultiMesh`](@ref). +with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), and [`T8codeMesh`](@ref). -A single tree per dimension is created and uniformly refined `initial_refinement_level` times, -yielding `2^initial_refinement_level` cells per dimension. +A single tree per dimension is created and uniformly refined `refinement_level` times, +yielding `2^refinement_level` cells per dimension. + +# Arguments +- `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension + to create a rectangular mesh. +- `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension + to create a rectangular mesh. Must have the same length as `coordinates_min`. +- `refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. +- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. Default: `1`. +- `RealT::Type`: the type that should be used for coordinates. Default: `Float64`. +- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` + deciding for each dimension if the boundaries in this dimension are periodic. Default: `false`. +- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. Default: `true`. +- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity + independent of domain partitioning. Should be `false` for static meshes + to permit more fine-grained partitioning. Default: `true`. """ function P4estMesh(; coordinates_min, coordinates_max, - initial_refinement_level, + refinement_level, polydeg = 1, RealT = Float64, periodicity = false, unsaved_changes = true, p4est_partition_allow_for_coarsening = true) + if length(coordinates_min) != length(coordinates_max) + throw(ArgumentError("coordinates_min and coordinates_max must have the same length")) + end NDIMS = length(coordinates_min) return P4estMesh(ntuple(_ -> 1, NDIMS); polydeg = polydeg, coordinates_min = coordinates_min, coordinates_max = coordinates_max, RealT = RealT, - initial_refinement_level = initial_refinement_level, + initial_refinement_level = refinement_level, periodicity = periodicity, unsaved_changes = unsaved_changes, p4est_partition_allow_for_coarsening = p4est_partition_allow_for_coarsening) diff --git a/src/meshes/structured_mesh.jl b/src/meshes/structured_mesh.jl index 4e2e2ba4604..f566be8688c 100644 --- a/src/meshes/structured_mesh.jl +++ b/src/meshes/structured_mesh.jl @@ -146,19 +146,33 @@ function StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; end """ - StructuredMesh(; coordinates_min, coordinates_max, initial_refinement_level, periodicity=false) + StructuredMesh(; coordinates_min, coordinates_max, refinement_level, periodicity=false) Create a rectangular `StructuredMesh` using keyword arguments only, for easy mesh-type swapping -with [`TreeMesh`](@ref), [`P4estMesh`](@ref), [`T8codeMesh`](@ref), and [`DGMultiMesh`](@ref). +with [`TreeMesh`](@ref), [`P4estMesh`](@ref), and [`T8codeMesh`](@ref). -The number of cells per dimension is `2^initial_refinement_level`. +The number of cells per dimension is `2^refinement_level`. + +Note that `StructuredMesh` does not support adaptive mesh refinement; +`refinement_level` only sets the initial uniform resolution. + +# Arguments +- `coordinates_min::NTuple{NDIMS, RealT}`: coordinate of the corner in the negative direction of each dimension. +- `coordinates_max::NTuple{NDIMS, RealT}`: coordinate of the corner in the positive direction of each dimension. + Must have the same length as `coordinates_min`. +- `refinement_level::Integer`: the number of cells in each dimension is set to `2^refinement_level`. +- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for + each dimension if the boundaries in this dimension are periodic. """ function StructuredMesh(; coordinates_min, coordinates_max, - initial_refinement_level, + refinement_level, periodicity = false) + if length(coordinates_min) != length(coordinates_max) + throw(ArgumentError("coordinates_min and coordinates_max must have the same length")) + end NDIMS = length(coordinates_min) - cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) + cells_per_dimension = ntuple(_ -> 2^refinement_level, NDIMS) return StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; periodicity = periodicity) end diff --git a/src/meshes/t8code_mesh.jl b/src/meshes/t8code_mesh.jl index 1db55d2149d..e45c46125f7 100644 --- a/src/meshes/t8code_mesh.jl +++ b/src/meshes/t8code_mesh.jl @@ -506,28 +506,30 @@ function T8codeMesh(trees_per_dimension; polydeg = 1, end """ - T8codeMesh(; coordinates_min, coordinates_max, initial_refinement_level, polydeg=1, kwargs...) + T8codeMesh(; coordinates_min, coordinates_max, refinement_level, polydeg=1, kwargs...) Create a rectangular `T8codeMesh` using keyword arguments only, for easy mesh-type swapping -with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and -[`DGMultiMesh`](@ref). +with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), and [`P4estMesh`](@ref). -A single tree per dimension is created and uniformly refined `initial_refinement_level` times, -yielding `2^initial_refinement_level` cells per dimension. +A single tree per dimension is created and uniformly refined `refinement_level` times, +yielding `2^refinement_level` cells per dimension. """ function T8codeMesh(; coordinates_min, coordinates_max, - initial_refinement_level, + refinement_level, polydeg = 1, RealT = Float64, periodicity = false) + if length(coordinates_min) != length(coordinates_max) + throw(ArgumentError("coordinates_min and coordinates_max must have the same length")) + end NDIMS = length(coordinates_min) return T8codeMesh(ntuple(_ -> 1, NDIMS); polydeg = polydeg, coordinates_min = coordinates_min, coordinates_max = coordinates_max, RealT = RealT, - initial_refinement_level = initial_refinement_level, + initial_refinement_level = refinement_level, periodicity = periodicity) end diff --git a/src/meshes/tree_mesh.jl b/src/meshes/tree_mesh.jl index 1cea806954b..b40e0dd3242 100644 --- a/src/meshes/tree_mesh.jl +++ b/src/meshes/tree_mesh.jl @@ -226,19 +226,21 @@ function TreeMesh(coordinates_min::Real, coordinates_max::Real; end """ - TreeMesh(; coordinates_min, coordinates_max, initial_refinement_level, n_cells_max, kwargs...) + TreeMesh(; coordinates_min, coordinates_max, refinement_level, n_cells_max, kwargs...) Create a [`TreeMesh`](@ref) using keyword arguments only, for easy mesh-type swapping -with [`StructuredMesh`](@ref), [`P4estMesh`](@ref), [`T8codeMesh`](@ref), and -[`DGMultiMesh`](@ref). +with [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and [`T8codeMesh`](@ref). """ function TreeMesh(; coordinates_min, coordinates_max, - initial_refinement_level, + refinement_level, n_cells_max, kwargs...) + if length(coordinates_min) != length(coordinates_max) + throw(ArgumentError("coordinates_min and coordinates_max must have the same length")) + end return TreeMesh(coordinates_min, coordinates_max; - initial_refinement_level = initial_refinement_level, + initial_refinement_level = refinement_level, n_cells_max = n_cells_max, kwargs...) end diff --git a/src/solvers/dgmulti/types.jl b/src/solvers/dgmulti/types.jl index 3441c672db6..41f317c5e2e 100644 --- a/src/solvers/dgmulti/types.jl +++ b/src/solvers/dgmulti/types.jl @@ -300,7 +300,7 @@ end """ DGMultiMesh(dg::DGMulti{NDIMS}; coordinates_min, coordinates_max, - initial_refinement_level, + refinement_level, is_on_boundary=nothing, periodicity=ntuple(_->false, NDIMS)) @@ -308,15 +308,18 @@ Create a rectangular `DGMultiMesh` using keyword arguments only, for easy mesh-t with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and [`T8codeMesh`](@ref). -The number of cells per dimension is `2^initial_refinement_level`. +The number of cells per dimension is `2^refinement_level`. """ function DGMultiMesh(dg::DGMulti{NDIMS}; coordinates_min, coordinates_max, - initial_refinement_level, + refinement_level, is_on_boundary = nothing, periodicity = ntuple(_ -> false, NDIMS)) where {NDIMS} - cells_per_dimension = ntuple(_ -> 2^initial_refinement_level, NDIMS) + if length(coordinates_min) != length(coordinates_max) + throw(ArgumentError("coordinates_min and coordinates_max must have the same length")) + end + cells_per_dimension = ntuple(_ -> 2^refinement_level, NDIMS) return DGMultiMesh(dg, cells_per_dimension; coordinates_min = coordinates_min, coordinates_max = coordinates_max, diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index d09560416ad..723ac593ff7 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -1029,10 +1029,9 @@ end coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) - # 2D: using initial_refinement_level - # polydeg defaults to 1 + # 2D: using refinement_level (polydeg defaults to 1) mesh_kw = P4estMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 2) + refinement_level = 2) @test mesh_kw isa P4estMesh{2} @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 # 1 macro-tree end diff --git a/test/test_special_elixirs.jl b/test/test_special_elixirs.jl index 146efd016f7..5aadf1b3c83 100644 --- a/test/test_special_elixirs.jl +++ b/test/test_special_elixirs.jl @@ -493,11 +493,6 @@ end @test_trixi_include(joinpath(examples_dir(), "special_elixirs", "elixir_euler_ad.jl")) end - - @timed_testset "elixir_advection_mesh_swap.jl" begin - @test_trixi_include(joinpath(examples_dir(), "special_elixirs", - "elixir_advection_mesh_swap.jl")) - end end end diff --git a/test/test_unit.jl b/test/test_unit.jl index 12d5488db69..c5fc731cfc4 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -99,7 +99,7 @@ end initial_refinement_level = 2, n_cells_max = 10_000) mesh_kw = TreeMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 2, n_cells_max = 10_000) + refinement_level = 2, n_cells_max = 10_000) @test Trixi.ncells(mesh_kw) == Trixi.ncells(mesh_ref) end @@ -3575,21 +3575,21 @@ end # 1D: keyword interface (2^2 = 4 cells per dimension) mesh_1d_ref = StructuredMesh((4,), (-1.0,), (1.0,)) mesh_1d_kw = StructuredMesh(; coordinates_min = (-1.0,), coordinates_max = (1.0,), - initial_refinement_level = 2) + refinement_level = 2) @test mesh_1d_ref.cells_per_dimension == mesh_1d_kw.cells_per_dimension # 2D: keyword interface mesh_2d_ref = StructuredMesh((4, 4), (-1.0, -1.0), (1.0, 1.0)) mesh_2d_kw = StructuredMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 2) + refinement_level = 2) @test mesh_2d_ref.cells_per_dimension == mesh_2d_kw.cells_per_dimension # 3D: keyword interface mesh_3d_ref = StructuredMesh((4, 4, 4), (-1.0, -1.0, -1.0), (1.0, 1.0, 1.0)) mesh_3d_kw = StructuredMesh(; coordinates_min = (-1.0, -1.0, -1.0), coordinates_max = (1.0, 1.0, 1.0), - initial_refinement_level = 2) + refinement_level = 2) @test mesh_3d_ref.cells_per_dimension == mesh_3d_kw.cells_per_dimension end @@ -3602,7 +3602,7 @@ end # 1D: keyword interface (2^2 = 4 elements) mesh_1d_ref = DGMultiMesh(dg_1d, (4,)) mesh_1d_kw = DGMultiMesh(dg_1d; coordinates_min = (-1.0,), coordinates_max = (1.0,), - initial_refinement_level = 2) + refinement_level = 2) @test mesh_1d_ref.md.num_elements == mesh_1d_kw.md.num_elements dg_2d = DGMulti(polydeg = 2, element_type = Quad(), @@ -3613,7 +3613,7 @@ end # 2D: keyword interface mesh_2d_ref = DGMultiMesh(dg_2d, (4, 4)) mesh_2d_kw = DGMultiMesh(dg_2d; coordinates_min = (-1.0, -1.0), - coordinates_max = (1.0, 1.0), initial_refinement_level = 2) + coordinates_max = (1.0, 1.0), refinement_level = 2) @test mesh_2d_ref.md.num_elements == mesh_2d_kw.md.num_elements end From 60ec7454ecebaa808f76d841e551c934e5446cc0 Mon Sep 17 00:00:00 2001 From: vincmarks Date: Mon, 18 May 2026 17:36:42 +0200 Subject: [PATCH 6/9] use refinement_level instead of initial_refinement_level in test/test_t8code_2d.jl --- test/test_t8code_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl index 31ee70fec12..6fc101ce244 100644 --- a/test/test_t8code_2d.jl +++ b/test/test_t8code_2d.jl @@ -321,7 +321,7 @@ end mesh_ref = T8codeMesh((4, 4); coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0)) mesh_kw = T8codeMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - initial_refinement_level = 2) + refinement_level = 2) @test mesh_kw isa T8codeMesh{2} @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 end From ebe65fc874927323d5e909c04619cbd39eeea011 Mon Sep 17 00:00:00 2001 From: vincmarks Date: Mon, 18 May 2026 21:51:05 +0200 Subject: [PATCH 7/9] add tests for length checking --- test/test_p4est_2d.jl | 3 +++ test/test_t8code_2d.jl | 3 +++ test/test_unit.jl | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index 723ac593ff7..298ce9129b1 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -1034,6 +1034,9 @@ end refinement_level = 2) @test mesh_kw isa P4estMesh{2} @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 # 1 macro-tree + @test_throws ArgumentError P4estMesh(; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + refinement_level = 2) end @trixi_testset "elixir_euler_imex_warm_bubble.jl" begin diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl index 6fc101ce244..106a7636bba 100644 --- a/test/test_t8code_2d.jl +++ b/test/test_t8code_2d.jl @@ -324,6 +324,9 @@ end refinement_level = 2) @test mesh_kw isa T8codeMesh{2} @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 + @test_throws ArgumentError T8codeMesh(; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + refinement_level = 2) end end diff --git a/test/test_unit.jl b/test/test_unit.jl index c5fc731cfc4..61e756bcfcd 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -101,6 +101,9 @@ end coordinates_max = (1.0, 1.0), refinement_level = 2, n_cells_max = 10_000) @test Trixi.ncells(mesh_kw) == Trixi.ncells(mesh_ref) + @test_throws ArgumentError TreeMesh(; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + refinement_level = 2, n_cells_max = 10_000) end @testset "helper functions" begin @@ -3591,6 +3594,9 @@ end coordinates_max = (1.0, 1.0, 1.0), refinement_level = 2) @test mesh_3d_ref.cells_per_dimension == mesh_3d_kw.cells_per_dimension + @test_throws ArgumentError StructuredMesh(; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + refinement_level = 2) end @testset "Unified mesh constructor signatures (DGMultiMesh)" begin @@ -3615,6 +3621,9 @@ end mesh_2d_kw = DGMultiMesh(dg_2d; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), refinement_level = 2) @test mesh_2d_ref.md.num_elements == mesh_2d_kw.md.num_elements + @test_throws ArgumentError DGMultiMesh(dg_2d; coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + refinement_level = 2) end end #module From 1eda211ee0bddb7052bff5ed9376ae7a6f361239 Mon Sep 17 00:00:00 2001 From: vincmarks Date: Mon, 18 May 2026 21:55:12 +0200 Subject: [PATCH 8/9] formatting --- test/test_t8code_2d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl index 106a7636bba..c1fc4c26e1e 100644 --- a/test/test_t8code_2d.jl +++ b/test/test_t8code_2d.jl @@ -325,8 +325,8 @@ end @test mesh_kw isa T8codeMesh{2} @test size(mesh_kw.tree_node_coordinates, ndims(mesh_kw) + 2) == 1 @test_throws ArgumentError T8codeMesh(; coordinates_min = (-1.0, -1.0), - coordinates_max = (1.0, 1.0, 1.0), - refinement_level = 2) + coordinates_max = (1.0, 1.0, 1.0), + refinement_level = 2) end end From 8554ea48684dd98f5451af006c6eeb69e478fa2a Mon Sep 17 00:00:00 2001 From: vincmarks Date: Wed, 20 May 2026 13:18:28 +0200 Subject: [PATCH 9/9] update doctrings, docs page, and use n_cell_max = nothing as default --- .../src/meshes/mesh_constructor_comparison.md | 35 +++---------------- src/meshes/p4est_mesh.jl | 5 ++- src/meshes/t8code_mesh.jl | 14 +++++++- src/meshes/tree_mesh.jl | 32 ++++++++++++++--- src/solvers/dgmulti/types.jl | 11 ++++++ test/test_unit.jl | 4 +-- 6 files changed, 63 insertions(+), 38 deletions(-) diff --git a/docs/src/meshes/mesh_constructor_comparison.md b/docs/src/meshes/mesh_constructor_comparison.md index 0d31c0c10c6..2e8ba3c8517 100644 --- a/docs/src/meshes/mesh_constructor_comparison.md +++ b/docs/src/meshes/mesh_constructor_comparison.md @@ -12,23 +12,11 @@ using `refinement_level`, yielding `2^refinement_level` cells per dimension. ```@example mesh-swap using Trixi -mesh = TreeMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - refinement_level = 2, n_cells_max = 30_000) -``` - -```@example mesh-swap -mesh = StructuredMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - refinement_level = 2) -``` - -```@example mesh-swap -mesh = P4estMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - refinement_level = 2) -``` - -```@example mesh-swap -mesh = T8codeMesh(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - refinement_level = 2) +for MeshType in (TreeMesh, StructuredMesh, P4estMesh, T8codeMesh) + mesh = MeshType(coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), + refinement_level = 2) + display(mesh) +end ``` [`DGMultiMesh`](@ref) also supports the same keyword arguments, but requires a solver `dg` @@ -44,16 +32,3 @@ Note: for `StructuredMesh` and `DGMultiMesh`, `refinement_level` directly sets `cells_per_dimension = 2^refinement_level`. For `P4estMesh` and `T8codeMesh`, a single tree per dimension is created and refined `refinement_level` times, which also yields `2^refinement_level` leaf cells per dimension. - -## Notes on `TreeMesh` - -[`TreeMesh`](@ref) has a different design and cannot be used as a -drop-in target in all cases: - -- It requires `n_cells_max` with no equivalent in other mesh types. -- It only supports refinement where all dimensions have the same number of cells (`2^level`). -- It only supports rectangular domains — no `mapping` or `faces`. - -When swapping **from** `TreeMesh` to another type, remove `n_cells_max`. -When swapping **to** `TreeMesh` from another type, add `n_cells_max` and ensure -the domain is rectangular. diff --git a/src/meshes/p4est_mesh.jl b/src/meshes/p4est_mesh.jl index f5ae6f8be37..88c66645c4e 100644 --- a/src/meshes/p4est_mesh.jl +++ b/src/meshes/p4est_mesh.jl @@ -246,7 +246,10 @@ function P4estMesh(trees_per_dimension; polydeg, end """ - P4estMesh(; coordinates_min, coordinates_max, refinement_level, polydeg=1, kwargs...) + P4estMesh(; coordinates_min, coordinates_max, refinement_level, + polydeg = 1, RealT = Float64, periodicity = false, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true) Create a rectangular `P4estMesh` using keyword arguments only, for easy mesh-type swapping with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), and [`T8codeMesh`](@ref). diff --git a/src/meshes/t8code_mesh.jl b/src/meshes/t8code_mesh.jl index e45c46125f7..5cfdf2df4b0 100644 --- a/src/meshes/t8code_mesh.jl +++ b/src/meshes/t8code_mesh.jl @@ -506,13 +506,25 @@ function T8codeMesh(trees_per_dimension; polydeg = 1, end """ - T8codeMesh(; coordinates_min, coordinates_max, refinement_level, polydeg=1, kwargs...) + T8codeMesh(; coordinates_min, coordinates_max, refinement_level, + polydeg = 1, RealT = Float64, periodicity = false) Create a rectangular `T8codeMesh` using keyword arguments only, for easy mesh-type swapping with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), and [`P4estMesh`](@ref). A single tree per dimension is created and uniformly refined `refinement_level` times, yielding `2^refinement_level` cells per dimension. + +# Arguments +- `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension + to create a rectangular mesh. Must have the same length as `coordinates_max`. +- `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension + to create a rectangular mesh. Must have the same length as `coordinates_min`. +- `refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. +- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. Default: `1`. +- `RealT::Type`: the type that should be used for coordinates. Default: `Float64`. +- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` + deciding for each dimension if the boundaries in this dimension are periodic. Default: `false`. """ function T8codeMesh(; coordinates_min, coordinates_max, diff --git a/src/meshes/tree_mesh.jl b/src/meshes/tree_mesh.jl index e06f8b1ac53..a5a8813f9af 100644 --- a/src/meshes/tree_mesh.jl +++ b/src/meshes/tree_mesh.jl @@ -237,23 +237,47 @@ function TreeMesh(coordinates_min::Real, coordinates_max::Real; end """ - TreeMesh(; coordinates_min, coordinates_max, refinement_level, n_cells_max, kwargs...) + TreeMesh(; coordinates_min, coordinates_max, refinement_level, + n_cells_max = nothing, periodicity = false, + refinement_patches = (), coarsening_patches = (), RealT = Float64) Create a [`TreeMesh`](@ref) using keyword arguments only, for easy mesh-type swapping with [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and [`T8codeMesh`](@ref). + +# Arguments +- `coordinates_min`: coordinates of the low corner of the domain as a tuple, + e.g. `(-1.0, -1.0)` for 2D. +- `coordinates_max`: coordinates of the high corner of the domain as a tuple. + Must have the same length as `coordinates_min`. +- `refinement_level::Integer`: number of uniform refinements; + yields `2^refinement_level` cells per dimension. +- `n_cells_max`: initial capacity of the mesh data structures. If `nothing` + (default), the capacity is derived from `refinement_level`. The mesh grows + automatically beyond the initial capacity when AMR requires more cells. +- `periodicity`: either a `Bool` applied to all dimensions or an `NTuple{NDIMS, Bool}` + specifying periodicity per dimension. Default: `false`. +- `refinement_patches`: regions to additionally refine. Default: `()`. +- `coarsening_patches`: regions to coarsen. Default: `()`. +- `RealT`: floating-point type for coordinates. Default: `Float64`. """ function TreeMesh(; coordinates_min, coordinates_max, refinement_level, - n_cells_max, - kwargs...) + n_cells_max = nothing, + periodicity = false, + refinement_patches = (), + coarsening_patches = (), + RealT = Float64) if length(coordinates_min) != length(coordinates_max) throw(ArgumentError("coordinates_min and coordinates_max must have the same length")) end return TreeMesh(coordinates_min, coordinates_max; initial_refinement_level = refinement_level, n_cells_max = n_cells_max, - kwargs...) + periodicity = periodicity, + refinement_patches = refinement_patches, + coarsening_patches = coarsening_patches, + RealT = RealT) end function Base.show(io::IO, mesh::TreeMesh{NDIMS, TreeType}) where {NDIMS, TreeType} diff --git a/src/solvers/dgmulti/types.jl b/src/solvers/dgmulti/types.jl index 41f317c5e2e..fc34ff94e1a 100644 --- a/src/solvers/dgmulti/types.jl +++ b/src/solvers/dgmulti/types.jl @@ -309,6 +309,17 @@ with [`TreeMesh`](@ref), [`StructuredMesh`](@ref), [`P4estMesh`](@ref), and [`T8codeMesh`](@ref). The number of cells per dimension is `2^refinement_level`. + +- `dg::DGMulti` contains information associated with the reference element (e.g., quadrature, + basis evaluation, differentiation, etc). +- `coordinates_min` is a vector or tuple of the coordinates of the corner in the negative direction of each dimension. + Must have the same length as `coordinates_max`. +- `coordinates_max` is a vector or tuple of the coordinates of the corner in the positive direction of each dimension. + Must have the same length as `coordinates_min`. +- `refinement_level` sets the number of cells per dimension to `2^refinement_level`. +- `is_on_boundary` specifies boundary using a `NamedTuple`. Default: `nothing`. +- `periodicity` is a tuple of booleans specifying if the domain is periodic `true`/`false` in the + (x,y,z) direction. Default: non-periodic in all dimensions. """ function DGMultiMesh(dg::DGMulti{NDIMS}; coordinates_min, diff --git a/test/test_unit.jl b/test/test_unit.jl index 68454e53a49..8576a65ccd2 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -100,11 +100,11 @@ end initial_refinement_level = 2, n_cells_max = 10_000) mesh_kw = TreeMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0), - refinement_level = 2, n_cells_max = 10_000) + refinement_level = 2) @test Trixi.ncells(mesh_kw) == Trixi.ncells(mesh_ref) @test_throws ArgumentError TreeMesh(; coordinates_min = (-1.0, -1.0), coordinates_max = (1.0, 1.0, 1.0), - refinement_level = 2, n_cells_max = 10_000) + refinement_level = 2) end @testset "helper functions" begin