Skip to content

3D Diffusion and Advection - #326

Draft
GeorgeR227 wants to merge 12 commits into
mainfrom
gr/3d-heat
Draft

3D Diffusion and Advection#326
GeorgeR227 wants to merge 12 commits into
mainfrom
gr/3d-heat

Conversation

@GeorgeR227

@GeorgeR227 GeorgeR227 commented Jun 10, 2025

Copy link
Copy Markdown
Collaborator

This PR is meant to serve as an initial lift to supporting 3D simulations in Decapodes. The initial commit includes an example simulation showcasing heat diffusion in a 3D cube, result shown below. This is based off a test created by @lukem12345 in CombinatorialSpaces.jl.

3d_heat.mp4

Changes to the Decapodes compiler and various changes/additions to DiagX will have to be made to support simulation generation. This PR can be considered complete once the above simulation can be run in a workflow that is essentially the same as the 2D heat diffusion simulation shown below.

using Decapodes
using DiagrammaticEquations
using CombinatorialSpaces
using GeometryBasics
using MLStyle
using ComponentArrays
using OrdinaryDiffEq
using Distributions
using CairoMakie

lx = ly = 10
s = triangulated_grid(lx, ly, 0.1, 0.1, Point3D)
sd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)
subdivide_duals!(sd, Circumcenter())

Heat = @decapode begin
    T::Form0
    D::Constant
    ∂ₜ(T) == D * Δ(T)
end

simulate = evalsim(Heat)
  
fₘ = simulate(sd, nothing)

T_dist = MvNormal([lx/2, ly/2], [1, 1])
T = [pdf(T_dist, [p[1], p[2]]) for p in sd[:point]]

D = 0.1

u₀ = ComponentArray(T=T)

constants_and_parameters = (D = D,)

tₑ = 100

prob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)
soln = solve(prob, Tsit5())

fig = Figure();
ax = CairoMakie.Axis(fig[1,1])
msh = CairoMakie.mesh!(ax, s, color=soln.u[end].T, colormap=:jet)
Colorbar(fig[1,2], msh)
save("2d_heat.png", fig)

@GeorgeR227 GeorgeR227 changed the title 3D Heat Diffusion 3D Diffusion and Advection Jun 13, 2025
@GeorgeR227

Copy link
Copy Markdown
Collaborator Author

Videos of 3D constant advection working along with a video of a projected down slab following the center of the disturbance. Note the relative lack of outward diffusion in the mass. There is some loss of mass of the advected ball as it moves along the domain, as per the bottom graph, which can be seen by the trailing wisps in the first video. However, this loss of mass seems not too great and could likely be improved with higher resolution.

3d_adv.mp4
3d_adv_slab.mp4

mass_adv

@DavidRobertsSBL

Copy link
Copy Markdown

What is the matter with the tests with this? I'm interested in expanding the physics library of standard equations, and something like Maxwell's equation(s) seems a fairly standard 'open' problem to add, in order to gain some experience with the system (perhaps by dropping myself in the deep end!). EM in 2+1-dimensions is a bit weird, the magnetic field is only a pseudoscalar, and there is no Gauss' law.

@GeorgeR227

Copy link
Copy Markdown
Collaborator Author

It's not so much the tests but the full range of features we would want for this PR have not yet been completed. There was an initial push a while ago to get 3D support but priorities changed and this got pushed onto the back-burner. However we're still interested in eventually getting 3D simulations fully supported so I'm glad we line up there!

I should be able to work on finishing support for this fairly soon, but if you'd like you can follow the example script in this PR and work on the equations purely using CombinatorialSpaces. There are a few things that need to be merged in for that as well but those should be completed for the most part.

@DavidRobertsSBL

Copy link
Copy Markdown

@GeorgeR227 ok, so just taking the following

using Decapodes
using DiagrammaticEquations
using CombinatorialSpaces
using GeometryBasics
using MLStyle
using ComponentArrays
using OrdinaryDiffEq
using GLMakie
using Distributions

lx = 100
ly = lz = 30

s = parallelepiped(lx = lx, ly = ly, lz = lz; tetcmd = "vpVq2a5")
sd = EmbeddedDeltaDualComplex3D{Bool, Float64, Point3D}(s)
subdivide_duals!(sd, Circumcenter())

# GLMakie.wireframe(s)

wdg_01 = dec_wedge_product(Tuple{0,1}, sd)

with the standard everything being imported (so no branches), the last line there fails:

MethodError: no method matching cache_wedge(::Type{Tuple{0, 1}}, ::EmbeddedDeltaDualComplex3D{Bool, Float64, Point{3, Float64}}, ::Type{Val{:CPU}}, ::typeof(identity), ::Nothing)
The function `cache_wedge` exists, but no method is defined for this combination of argument types.

And it suggests the 1-d and 2- versions from CombinatorialSpaces. However, I see DiscreteExteriorCalculus.jl does export this function! So not sure what's going on there.

Perhaps we should continue the discussion on my specific needs in a different place, or is here ok?

@GeorgeR227

GeorgeR227 commented Aug 1, 2025

Copy link
Copy Markdown
Collaborator Author

To get this to work, you'd have to switch over to use the gr/3d_dec_ops branch on CombinatorialSpaces.jl. The issue is that the current version doesn't allow 3D meshes to be used but the updates on that branch fix that. This is one of those things I'm planning to merge soon.

If you're in the Julia Zulip we can continue there or if not, you can email me at grauta@ufl.edu.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Initial lift toward 3D simulation support in Decapodes by extending operator expansion/codegen paths to dimension=3 and adding example scripts + tests around 3D DEC operators.

Changes:

  • Enable gensim(...; dimension=3) and extend form allocation/type handling for 3D forms.
  • Add 3D operator opening support (Lie derivatives, codifferential, de Rham Laplacians) and extend default DEC operator generation mappings.
  • Add new 2D/3D diffusion/advection example scripts plus 3D operator-opening tests.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/simulation.jl Allows dimension=3 in gensim, adds 3D form allocation mapping, and switches form detection to FORM_TYPES.
src/operators.jl Extends default operator generation and operator opening to 3D (⋆₃, d₂, dual_d₂, new wedges, 3D Laplacians/Lie derivatives).
test/simulation_core.jl Updates dimension fuzzing test to reflect that dimension=3 is now supported.
test/operators.jl Adds a new testset asserting 3D operator-opening expansions match expected ACSet structure.
examples/diff_adv/heat.jl Updates 2D heat example to use parameterized diffusion constant + Makie visualization.
examples/diff_adv/adv.jl Adds a 2D advection example with visualization/recording.
examples/diff_adv/3d_heat.jl Adds a 3D heat diffusion example on a tetrahedralized cube with GLMakie visualization/recording.
examples/diff_adv/3d_adv.jl Adds a 3D advection example (DualForm0 transport) with visualization/recording.
examples/diff_adv/test_adv.jl Adds an exploratory script comparing a codifferential-style construction in 2D vs 3D.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/simulation.jl
type == :DualForm0 || type == :DualForm1 || type == :DualForm2)
end

is_form(d::SummationDecapode, var_id::Int) = return (d[var_id, :type] in FORM_TYPES)

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_form is defined using = return ..., which is invalid Julia syntax in a short-form function definition and will prevent this file from parsing. Drop the return and just return the boolean expression.

Copilot uses AI. Check for mistakes.
Comment thread src/simulation.jl
Comment on lines 645 to 647
function gensim(user_d::SummationDecapode, input_vars::Vector{Symbol}; dimension::Int=2, stateeltype::DataType = Float64, code_target::AbstractGenerationTarget = CPUTarget(), preallocate::Bool = true, contract::Bool = true, multigrid::Bool = false)
(dimension == 1 || dimension == 2) ||
(dimension == 1 || dimension == 2 || dimension == 3) ||
throw(UnsupportedDimensionException(dimension))

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gensim docstring immediately above still states that dimension “Must be 1 or 2”, but this guard now allows 3. Please update the documentation so the public API contract matches the implementation.

Copilot uses AI. Check for mistakes.
Comment thread src/operators.jl
Comment on lines 90 to +93
# Inverse Hodge Stars
:⋆₀⁻¹ => dec_inv_hodge_star(0, sd, hodge) |> matmul
:⋆₁⁻¹ => dec_pair_inv_hodge(Val{1}, sd, hodge) # Special since Geo is a solver
:⋆₂⁻¹ => dec_inv_hodge_star(1, sd, hodge) |> matmul
:⋆₂⁻¹ => dec_inv_hodge_star(2, sd, hodge) |> matmul # TODO: Needs pair for 3D Geo

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default_dec_matrix_generate can emit :⋆₁⁻¹ via dec_pair_inv_hodge(Val{1}, sd, hodge), but the only GeometricHodge method is specialized to AbstractDeltaDualComplex2D. With the new 3D operator expansions (which introduce ⋆₁⁻¹), running a 3D simulation with the default hodge=GeometricHodge() will throw a MethodError. Add a 3D GeometricHodge implementation (or change the default/emit a clearer error) so dimension=3 workflows don’t fail by default.

Copilot uses AI. Check for mistakes.
Comment thread src/operators.jl
Comment on lines 151 to 155
function dec_pair_wedge_product(::Type{Tuple{1,1}}, sd::HasDeltaSet2D)
val_pack = cache_wedge(Tuple{1,1}, sd, Val{:CPU})
((y, α, β) -> dec_c_wedge_product!(Tuple{1,1}, y, α, β, val_pack[1], val_pack[2]),
(α, β) -> dec_c_wedge_product(Tuple{1,1}, α, β, val_pack))
end

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dec_pair_wedge_product(Tuple{1,1}, ...) is only implemented for HasDeltaSet2D, but the new 3D operator expansions produce ∧₁₁ (e.g. from L₁/L₂). This will cause a MethodError during 3D codegen/execution when default_dec_matrix_generate tries to generate :∧₁₁ on a 3D complex. Add a HasDeltaSet3D (or more general HasDeltaSet) method for the (1,1) wedge product, assuming cache_wedge supports it.

Copilot uses AI. Check for mistakes.
Comment thread examples/diff_adv/adv.jl
Comment on lines +14 to +19
lx = 100
ly = 30

s = triangulated_grid(lx, ly, RESOLUTION, RESOLUTION, Point3D)
sd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)
subdivide_duals!(sd, Circumcenter())

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point3D is used as the point type for triangulated_grid/EmbeddedDeltaDualComplex2D, but it isn’t defined anywhere in this file (most other examples define Point3D = Point3{Float64} or use Point3d). As written, this script will fail to run.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +32
T_dist = MvNormal([lx/2, ly/2, lx/2], [1, 1, 1])
T = [pdf(T_dist, [p[1], p[2], p[3]]) for p in sd[:point]]

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 3D Gaussian initial condition uses MvNormal([lx/2, ly/2, lx/2], ...); the third component should be centered using lz/2 (or another z-center), not lx/2. This currently mis-centers the distribution along z.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +15
s = parallelepiped(lx = lx, ly = ly, lz = lz; tetcmd = "vpVq2a5")
sd = EmbeddedDeltaDualComplex3D{Bool, Float64, Point3D}(s)
subdivide_duals!(sd, Barycenter())

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example uses Point3D (and later Point3d) but does not import GeometryBasics or define the Point3D alias. Most other examples define Point3D = Point3{Float64} (or consistently use Point3d). As written it will error when constructing EmbeddedDeltaDualComplex3D / evaluating the constant form.

Copilot uses AI. Check for mistakes.
Comment thread examples/diff_adv/heat.jl
Comment on lines +11 to +14
lx = ly = 10
s = triangulated_grid(lx, ly, 0.1, 0.1, Point3D)
sd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)
subdivide_duals!(sd, Circumcenter())

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example uses Point3D as the point type in triangulated_grid/EmbeddedDeltaDualComplex2D, but Point3D is never defined in this file. Other examples define it as Point3{Float64} (or use Point3d). As written, the example will error on load.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +9
using CombinatorialSpaces

s = EmbeddedDeltaSet3D{Bool,Point3d}()
add_vertices!(s, 4, point=[Point3d(0,0,0), Point3d(1,0,0),
Point3d(0,1,0), Point3d(0,0,1)])
glue_tetrahedron!(s, 1, 2, 3, 4)
orient!(s)
sd = EmbeddedDeltaDualComplex3D{Bool, Float64, Point3D}(s)
subdivide_duals!(sd, Circumcenter())

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script uses Point3d and Point3D but only imports CombinatorialSpaces. Point3d/Point3{Float64} come from GeometryBasics, and Point3D isn’t defined here. As written, the example will error on load; import GeometryBasics and define the point type consistently.

Copilot uses AI. Check for mistakes.
Comment thread src/simulation.jl
Comment on lines 645 to 647
function gensim(user_d::SummationDecapode, input_vars::Vector{Symbol}; dimension::Int=2, stateeltype::DataType = Float64, code_target::AbstractGenerationTarget = CPUTarget(), preallocate::Bool = true, contract::Bool = true, multigrid::Bool = false)
(dimension == 1 || dimension == 2) ||
(dimension == 1 || dimension == 2 || dimension == 3) ||
throw(UnsupportedDimensionException(dimension))

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dimension=3 is now accepted by gensim, but the test suite only exercises 3D at the operator-expansion level (and updates the “unsupported dimension” fuzzing test). Consider adding at least one end-to-end 3D gensim/evalsim smoke test (e.g., compile a simple 3D heat decapode and ensure the generated RHS runs on a tiny 3D complex) to catch missing runtime operator generators like wedges/inverse hodges.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants