From 6c2a1258a2be79433fb6ee321940a6e9c9cb3939 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Thu, 13 Jun 2024 20:06:06 +0000 Subject: [PATCH] build based on 7738216 --- dev/.documenter-siteinfo.json | 2 +- dev/api/index.html | 4 +- dev/ascii/index.html | 2 +- dev/bc/bc_debug/index.html | 4 +- dev/bsh/budyko_sellers_halfar.jld2 | Bin 1338817 -> 1338817 bytes dev/bsh/budyko_sellers_halfar/index.html | 6 +-- dev/canon/index.html | 64 +++++++++++------------ dev/ch/cahn-hilliard/index.html | 6 +-- dev/cism/cism/index.html | 20 +++---- dev/cism/ice_dynamics2D.jld2 | Bin 31330417 -> 31330417 bytes dev/equations/equations/index.html | 2 +- dev/grigoriev/grigoriev.jld2 | Bin 10749416 -> 10749416 bytes dev/grigoriev/grigoriev/index.html | 6 +-- dev/halmo/halmo/index.html | 8 +-- dev/ice_dynamics/ice_dynamics/index.html | 10 ++-- dev/ice_dynamics/ice_dynamics1D.jld2 | Bin 205230 -> 205230 bytes dev/ice_dynamics/ice_dynamics2D.jld2 | Bin 713608 -> 713608 bytes dev/index.html | 2 +- dev/klausmeier/klausmeier/index.html | 6 +-- dev/navier_stokes/ns/index.html | 2 +- dev/nhs/nhs_lite/index.html | 4 +- dev/objects.inv | Bin 2290 -> 2428 bytes dev/overview/overview/index.html | 4 +- dev/poiseuille/poiseuille/index.html | 2 +- dev/search_index.js | 2 +- 25 files changed, 78 insertions(+), 78 deletions(-) diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index f973fac8..83a31137 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.4","generation_timestamp":"2024-06-13T15:13:43","documenter_version":"1.4.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.4","generation_timestamp":"2024-06-13T20:04:40","documenter_version":"1.4.1"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index badfcd26..5d6648b0 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -1,3 +1,3 @@ -Library Reference · Decapodes.jl

Library Reference

Decapodes

Decapodes.gensimMethod
function gensim(d::AbstractNamedDecapode; dimension::Int=2)

Generate a simulation function from the given Decapode. The returned function can then be combined with a mesh and a function describing function mappings to return a simulator to be passed to solve.

source
[ Info: Page built in 2 seconds.
-[ Info: This page was last built at 2024-06-13T15:03:58.554.
+Library Reference · Decapodes.jl

Library Reference

Decapodes

Decapodes.compileMethod
compile(d::SummationDecapode, inputs::Vector{Symbol}, alloc_vectors::Vector{AllocVecCall}, optimizable_dec_operators::Set{Symbol}, dimension::Int, stateeltype::DataType, code_target::GenerationTarget)

Function that compiles the computation body. d is the input Decapode, inputs is a vector of state variables and literals, alloc_vec should be empty when passed in, optimizable_dec_operators is a collection of all DEC operator symbols that can use special in-place methods, dimension is the dimension of the problem (usually 1 or 2), stateeltype is the type of the state elements (usually Float32 or Float64) and code_target determines what architecture the code is compiled for (either CPU or CUDA).

source
Decapodes.compile_envMethod
compile_env(d::SummationDecapode, dec_matrices::Vector{Symbol}, con_dec_operators::Set{Symbol}, code_target::GenerationTarget)

This creates the symbol to function linking for the simulation output. Those run through the default_dec backend expect both an in-place and an out-of-place variant in that order. User defined operations only support out-of-place.

source
Decapodes.gensimMethod
gensim(user_d::SummationDecapode, input_vars::Vector{Symbol}; dimension::Int=2, stateeltype::DataType = Float64, code_target::GenerationTarget = CPUTarget())

Generates the entire code body for the simulation function. user_d is the user passed Decapodes which will be left unmodified and 'input_vars' is the collection of state variables and literals in the Decapode.

Optional keyword arguments are dimension, which is the dimension of the problem and defaults to 2D, stateeltype, which is the element type of the state forms and defaults to Float64 and code_target, which is the intended architecture target for the generated code, defaulting to regular CPU compatible code.

source
Decapodes.gensimMethod
function gensim(d::SummationDecapode; dimension::Int=2)

Generate a simulation function from the given Decapode. The returned function can then be combined with a mesh and a function describing function mappings to return a simulator to be passed to solve.

source
[ Info: Page built in 2 seconds.
+[ Info: This page was last built at 2024-06-13T19:54:46.887.
diff --git a/dev/ascii/index.html b/dev/ascii/index.html index 264bf988..4bbd9404 100644 --- a/dev/ascii/index.html +++ b/dev/ascii/index.html @@ -1,3 +1,3 @@ ASCII Operators · Decapodes.jl

ASCII and Vector Calculus Operators

Some users may have trouble entering unicode characters like ⋆ or ∂ in their development environment. So, we offer the following ASCII equivalents. Further, some users may like to use vector calculus symbols instead of exterior calculus symbols where possible. We offer support for such symbols as well.

ASCII Equivalents

UnicodeASCIIMeaning
∂ₜdtderivative w.r.t. time
starHodge star, generalizing transpose
Δlapllaplacian
wedgewedge product, generalizing the cross product
⋆⁻¹star_invHodge star, generalizing transpose
∘(⋆,d,⋆)divdivergence, a.k.a. ∇⋅
avg₀₁avg_01average values stored on endpoints of edges

Vector Calculus Equivalents

VecDECHow to enter
graddgrad
div∘(⋆,d,⋆)div
curl∘(d,⋆)curl
d\nabla
∇ᵈ∘(⋆,d,⋆)\nabla \<tab\> \^d \<tab\>
∇x∘(d,⋆)\nabla \<tab\> x
adv(X,Y)∘(⋆,d,⋆)(X∧Y)adv
[ Info: Page built in 0 seconds.
-[ Info: This page was last built at 2024-06-13T15:03:58.705.
+[ Info: This page was last built at 2024-06-13T19:54:47.050. diff --git a/dev/bc/bc_debug/index.html b/dev/bc/bc_debug/index.html index 6079f933..ffe1328c 100644 --- a/dev/bc/bc_debug/index.html +++ b/dev/bc/bc_debug/index.html @@ -340,5 +340,5 @@ # Animation record(fig, "diff_adv_right.gif", range(0.0, 100.0; length=150); framerate = 30) do t pmsh.color = sol(t).C -end
"diff_adv_right.gif"

Diffusion-Advection result and your first BC Decapode!

[ Info: Page built in 100 seconds.
-[ Info: This page was last built at 2024-06-13T15:05:38.983.
+end
"diff_adv_right.gif"

Diffusion-Advection result and your first BC Decapode!

[ Info: Page built in 101 seconds.
+[ Info: This page was last built at 2024-06-13T19:56:27.787.
diff --git a/dev/bsh/budyko_sellers_halfar.jld2 b/dev/bsh/budyko_sellers_halfar.jld2 index a2b0e8a10d329770af658b04f85a86673fc3953b..0a7506c2a3edb249ae2d1b728fffafa2509bbea3 100644 GIT binary patch delta 99 zcmWN=yA6OK6hP7N{6D|oDlEbz_OuqRV`5=xn1dlKT)=yhlX6HooK}s$oW7g!yTn7H bp|D`diZvUy?5ONHaOA|93oYF(aeclY8G;@a delta 99 zcmWN=yA6OK6hP7N{6D|oCTzka_OuqRV`5=xn1dlKT)=yhlX6HooK}s$oW7g!yTn7H bp|D`diZvUy?5ONHaOA|93oYF(aeclY7%UzU diff --git a/dev/bsh/budyko_sellers_halfar/index.html b/dev/bsh/budyko_sellers_halfar/index.html index 258d8faa..fa9965ee 100644 --- a/dev/bsh/budyko_sellers_halfar/index.html +++ b/dev/bsh/budyko_sellers_halfar/index.html @@ -419,7 +419,7 @@ end return (args...) -> op(args...) end
generate (generic function with 1 method)

Simulation generation

From our Decapode, we automatically generate a finite difference method solver that performs explicit time-stepping to solve our system of multiphysics equations.

sim = eval(gensim(budyko_sellers_halfar, dimension=1))
-fₘ = sim(sd, generate)
(::Main.var"#f#42"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int64}, Main.var"#26#35"{Main.var"#18#27"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex1D{Bool, Float64, GeometryBasics.Point{2, Float64}}}}, Main.var"#26#35"{Main.var"#25#34"}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Run simulation

We wrap our simulator and initial conditions and solve them with the stability-detection and time-stepping methods provided by DifferentialEquations.jl .

tₑ = 1e6
+fₘ = sim(sd, generate)
(::Main.var"#f#44"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 11}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int64}, Main.var"#26#35"{Main.var"#18#27"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex1D{Bool, Float64, GeometryBasics.Point{2, Float64}}}}, Main.var"#26#35"{Main.var"#25#34"}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Run simulation

We wrap our simulator and initial conditions and solve them with the stability-detection and time-stepping methods provided by DifferentialEquations.jl .

tₑ = 1e6
 
 @info("Solving")
 prob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)
@@ -427,5 +427,5 @@
 @show soln.retcode
 @info("Done")
[ Info: Solving
 soln.retcode = SciMLBase.ReturnCode.Success
-[ Info: Done

We can save the solution file to examine later.

@save "budyko_sellers_halfar.jld2" soln

Visualize

Quickly examine the final conditions for temperature:

lines(map(x -> x[1], point(s)), soln(tₑ).Tₛ)
Example block output

Quickly examine the final conditions for ice height:

lines(map(x -> x[1], point(s)), soln(tₑ).halfar_dynamics_h)
Example block output

BSH_Temperature

BSH_IceHeight

[ Info: Page built in 47 seconds.
-[ Info: This page was last built at 2024-06-13T15:06:26.735.
+[ Info: Done

We can save the solution file to examine later.

@save "budyko_sellers_halfar.jld2" soln

Visualize

Quickly examine the final conditions for temperature:

lines(map(x -> x[1], point(s)), soln(tₑ).Tₛ)
Example block output

Quickly examine the final conditions for ice height:

lines(map(x -> x[1], point(s)), soln(tₑ).halfar_dynamics_h)
Example block output

BSH_Temperature

BSH_IceHeight

[ Info: Page built in 49 seconds.
+[ Info: This page was last built at 2024-06-13T19:57:17.628.
diff --git a/dev/canon/index.html b/dev/canon/index.html index 57de32b1..4cda8116 100644 --- a/dev/canon/index.html +++ b/dev/canon/index.html @@ -3,37 +3,37 @@ (D, cosϕᵖ, cosϕᵈ)::Constant -HT == (D ./ cosϕᵖ) .* (⋆)(d(cosϕᵈ .* (⋆)(d(Tₛ))))source
Decapodes.Canon.Physics.:outgoing_longwave_radiationConstant

Outgoing Longwave Radiation

Source

Model

(Tₛ, OLR)::Form0
+HT == (D ./ cosϕᵖ) .* (⋆)(d(cosϕᵈ .* (⋆)(d(Tₛ))))
source
Decapodes.Canon.Physics.:outgoing_longwave_radiationConstant

Outgoing Longwave Radiation

Source

Model

(Tₛ, OLR)::Form0
               
 (A, B)::Constant
               
-OLR == A .+ B .* Tₛ
source
Decapodes.Canon.Physics.absorbed_shortwave_radiationConstant

Absorbed Shortwave Radiation

Source

The proportion of light reflected by a surface is the albedo. The absorbed shortwave radiation is the complement of this quantity.

Model

(Q, ASR)::Form0
+OLR == A .+ B .* Tₛ
source
Decapodes.Canon.Physics.absorbed_shortwave_radiationConstant

Absorbed Shortwave Radiation

Source

The proportion of light reflected by a surface is the albedo. The absorbed shortwave radiation is the complement of this quantity.

Model

(Q, ASR)::Form0
               
 α::Constant
               
-ASR == (1 .- α) .* Q
source
Decapodes.Canon.Physics.advectionConstant

Advection

Source

Advection refers to the transport of a bulk along a vector field.

Model

C::Form0
+ASR == (1 .- α) .* Q
source
Decapodes.Canon.Physics.advectionConstant

Advection

Source

Advection refers to the transport of a bulk along a vector field.

Model

C::Form0
               
 (ϕ, V)::Form1
               
-ϕ == C ∧₀₁ V
source
Decapodes.Canon.Physics.ficks_lawConstant

Ficks Law

Source

Equation for diffusion first stated by Adolf Fick. The diffusion flux is proportional to the concentration gradient.

Model

C::Form0
+ϕ == C ∧₀₁ V
source
Decapodes.Canon.Physics.ficks_lawConstant

Ficks Law

Source

Equation for diffusion first stated by Adolf Fick. The diffusion flux is proportional to the concentration gradient.

Model

C::Form0
               
 ϕ::Form1
               
-ϕ == k(d₀(C))
source
Decapodes.Canon.Physics.iceblockingwaterConstant

IceBlockingWater

Source

Model

h::Form0
+ϕ == k(d₀(C))
source
Decapodes.Canon.Physics.iceblockingwaterConstant

IceBlockingWater

Source

Model

h::Form0
               
 (𝐮, w)::DualForm1
               
-w == (1 - σ(h)) ∧ᵖᵈ₀₁ 𝐮
source
Decapodes.Canon.Physics.jko_schemeConstant

Jordan-Kinderlehrer-Otto

Source

Jordan, R., Kinderlehrer, D., & Otto, F. (1998). The Variational Formulation of the Fokker–Planck Equation. In SIAM Journal on Mathematical Analysis (Vol. 29, Issue 1, pp. 1–17). Society for Industrial & Applied Mathematics (SIAM). https://doi.org/10.1137/s0036141096303359

Model

(ρ, Ψ)::Form0
+w == (1 - σ(h)) ∧ᵖᵈ₀₁ 𝐮
source
Decapodes.Canon.Physics.jko_schemeConstant

Jordan-Kinderlehrer-Otto

Source

Jordan, R., Kinderlehrer, D., & Otto, F. (1998). The Variational Formulation of the Fokker–Planck Equation. In SIAM Journal on Mathematical Analysis (Vol. 29, Issue 1, pp. 1–17). Society for Industrial & Applied Mathematics (SIAM). https://doi.org/10.1137/s0036141096303359

Model

(ρ, Ψ)::Form0
               
 β⁻¹::Constant
               
-∂ₜ(ρ) == (∘(⋆, d, ⋆))(d(Ψ) ∧ ρ) + β⁻¹ * Δ(ρ)
source
Decapodes.Canon.Physics.lieConstant

Lie

Source

Model

C::Form0
+∂ₜ(ρ) == (∘(⋆, d, ⋆))(d(Ψ) ∧ ρ) + β⁻¹ * Δ(ρ)
source
Decapodes.Canon.Physics.lieConstant

Lie

Source

Model

C::Form0
               
 V::Form1
               
 dX::Form1
               
-V == ((⋆) ∘ (⋆))(C ∧ dX)
source
Decapodes.Canon.Physics.mohamed_flowConstant

Mohamed Eq. 10, N2

Source

Model

(𝐮, w)::DualForm1
+V == ((⋆) ∘ (⋆))(C ∧ dX)
source
Decapodes.Canon.Physics.mohamed_flowConstant

Mohamed Eq. 10, N2

Source

Model

(𝐮, w)::DualForm1
               
 (P, 𝑝ᵈ)::DualForm0
               
@@ -41,7 +41,7 @@
               
 𝑝ᵈ == P + 0.5 * ι₁₁(w, w)
               
-∂ₜ(𝐮) == μ * (∘(d, ⋆, d, ⋆))(w) + -1 * (⋆₁⁻¹)(w ∧ᵈᵖ₁₀ (⋆)(d(w))) + d(𝑝ᵈ)
source
Decapodes.Canon.Physics.momentumConstant

Momentum

Source

Model

(f, b)::Form0
+∂ₜ(𝐮) == μ * (∘(d, ⋆, d, ⋆))(w) + -1 * (⋆₁⁻¹)(w ∧ᵈᵖ₁₀ (⋆)(d(w))) + d(𝑝ᵈ)
source
Decapodes.Canon.Physics.momentumConstant

Momentum

Source

Model

(f, b)::Form0
               
 (v, V, g, Fᵥ, uˢ, v_up)::Form1
               
@@ -53,7 +53,7 @@
               
 v_up == (((((((-1 * L(v, v) - L(V, v)) - L(v, V)) - f ∧ v) - (∘(⋆, d, ⋆))(uˢ) ∧ v) - d(p)) + b ∧ g) - (∘(⋆, d, ⋆))(τ)) + uˢ̇ + Fᵥ
               
-uˢ̇ == force(U)
source
Decapodes.Canon.Physics.navier_stokesConstant

Navier-Stokes

Source

Partial differential equations which describe the motion of viscous fluid surfaces.

Model

(V, V̇, G)::Form1{X}
+uˢ̇ == force(U)
source
Decapodes.Canon.Physics.navier_stokesConstant

Navier-Stokes

Source

Partial differential equations which describe the motion of viscous fluid surfaces.

Model

(V, V̇, G)::Form1{X}
               
 (ρ, ṗ, p)::Form0{X}
               
@@ -63,7 +63,7 @@
               
 ṗ == neg₀((⋆₀⁻¹)(L₀(V, (⋆₀)(p))))
               
-∂ₜ(p) == ṗ
source
Decapodes.Canon.Physics.oscillatorConstant

Oscillator

Source

Equation governing the motion of an object whose acceleration is negatively-proportional to its position.

Model

X::Form0
+∂ₜ(p) == ṗ
source
Decapodes.Canon.Physics.oscillatorConstant

Oscillator

Source

Equation governing the motion of an object whose acceleration is negatively-proportional to its position.

Model

X::Form0
               
 V::Form0
               
@@ -71,7 +71,7 @@
               
 ∂ₜ(X) == V
               
-∂ₜ(V) == -k * X
source
Decapodes.Canon.Physics.poiseuilleConstant

Poiseuille

Source

A relation between the pressure drop in an incompressible and Newtownian fluid in laminar flow flowing through a long cylindrical pipe.

Model

P::Form0
+∂ₜ(V) == -k * X
source
Decapodes.Canon.Physics.poiseuilleConstant

Poiseuille

Source

A relation between the pressure drop in an incompressible and Newtownian fluid in laminar flow flowing through a long cylindrical pipe.

Model

P::Form0
               
 q::Form1
               
@@ -83,7 +83,7 @@
               
 ∂ₜ(q) == q̇
               
-q̇ == μ̃ * ∂q(Δq) + ∇P + R * q
source
Decapodes.Canon.Physics.poiseuille_densityConstant

Poiseuille Density

Source

Model

q::Form1
+q̇ == μ̃ * ∂q(Δq) + ∇P + R * q
source
Decapodes.Canon.Physics.poiseuille_densityConstant

Poiseuille Density

Source

Model

q::Form1
               
 (P, ρ)::Form0
               
@@ -101,13 +101,13 @@
               
 ρ_up == (∘(⋆, d, ⋆))(-1 * (ρ ∧₀₁ q))
               
-ρ̇ == ∂ρ(ρ_up)
source
Decapodes.Canon.Physics.schroedingerConstant

Schroedinger

Source

The evolution of the wave function over time.

Model

(i, h, m)::Constant
+ρ̇ == ∂ρ(ρ_up)
source
Decapodes.Canon.Physics.schroedingerConstant

Schroedinger

Source

The evolution of the wave function over time.

Model

(i, h, m)::Constant
               
 V::Parameter
               
 Ψ::Form0
               
-∂ₜ(Ψ) == (((-1 * h ^ 2) / (2m)) * Δ(Ψ) + V * Ψ) / (i * h)
source
Decapodes.Canon.Physics.superpositionConstant

Superposition

Source

Model

(C, Ċ)::Form0
+∂ₜ(Ψ) == (((-1 * h ^ 2) / (2m)) * Δ(Ψ) + V * Ψ) / (i * h)
source
Decapodes.Canon.Physics.superpositionConstant

Superposition

Source

Model

(C, Ċ)::Form0
               
 (ϕ, ϕ₁, ϕ₂)::Form1
               
@@ -115,7 +115,7 @@
               
 Ċ == (⋆₀⁻¹)(dual_d₁((⋆₁)(ϕ)))
               
-∂ₜ(C) == Ċ
source

Chemistry

Decapodes.Canon.Chemistry.GrayScottConstant

Gray-Scott

Source

A model of reaction-diffusion

Model

(U, V)::Form0
+∂ₜ(C) == Ċ
source

Chemistry

Decapodes.Canon.Chemistry.GrayScottConstant

Gray-Scott

Source

A model of reaction-diffusion

Model

(U, V)::Form0
               
 UV2::Form0
               
@@ -131,7 +131,7 @@
               
 ∂ₜ(U) == U̇
               
-∂ₜ(V) == V̇
source
Decapodes.Canon.Chemistry.brusselatorConstant

Brusselator

Source

A model of reaction-diffusion for an oscillatory chemical system.

Model

(U, V)::Form0
+∂ₜ(V) == V̇
source
Decapodes.Canon.Chemistry.brusselatorConstant

Brusselator

Source

A model of reaction-diffusion for an oscillatory chemical system.

Model

(U, V)::Form0
               
 U2V::Form0
               
@@ -149,33 +149,33 @@
               
 ∂ₜ(U) == U̇
               
-∂ₜ(V) == V̇
source

Biology

Decapodes.Canon.Biology.kealyConstant

Kealy

Source

Model

(n, w)::DualForm0
+∂ₜ(V) == V̇
source

Biology

Decapodes.Canon.Biology.kealyConstant

Kealy

Source

Model

(n, w)::DualForm0
               
 dX::Form1
               
 (a, ν)::Constant
               
-∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * Δ(w)
source
Decapodes.Canon.Biology.klausmeier_2aConstant

Klausmeier (Eq. 2a)

Source

Klausmeier, CA. “Regular and irregular patterns in semiarid vegetation.” Science (New York, N.Y.) vol. 284,5421 (1999): 1826-8. doi:10.1126/science.284.5421.1826

Model

(n, w)::DualForm0
+∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * Δ(w)
source
Decapodes.Canon.Biology.klausmeier_2aConstant

Klausmeier (Eq. 2a)

Source

Klausmeier, CA. “Regular and irregular patterns in semiarid vegetation.” Science (New York, N.Y.) vol. 284,5421 (1999): 1826-8. doi:10.1126/science.284.5421.1826

Model

(n, w)::DualForm0
               
 dX::Form1
               
 (a, ν)::Constant
               
-∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * ℒ(dX, w)
source
Decapodes.Canon.Biology.klausmeier_2bConstant

Klausmeier (Eq. 2b)

Source

ibid.

Model

(n, w)::DualForm0
+∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * ℒ(dX, w)
source
Decapodes.Canon.Biology.klausmeier_2bConstant

Klausmeier (Eq. 2b)

Source

ibid.

Model

(n, w)::DualForm0
               
 m::Constant
               
-∂ₜ(n) == (w * n ^ 2 - m * n) + Δ(n)
source
Decapodes.Canon.Biology.lejeuneConstant

Lejeune

Source

Lejeune, O., & Tlidi, M. (1999). A Model for the Explanation of Vegetation Stripes (Tiger Bush). Journal of Vegetation Science, 10(2), 201–208. https://doi.org/10.2307/3237141

Model

ρ::Form0
+∂ₜ(n) == (w * n ^ 2 - m * n) + Δ(n)
source
Decapodes.Canon.Biology.lejeuneConstant

Lejeune

Source

Lejeune, O., & Tlidi, M. (1999). A Model for the Explanation of Vegetation Stripes (Tiger Bush). Journal of Vegetation Science, 10(2), 201–208. https://doi.org/10.2307/3237141

Model

ρ::Form0
               
 (μ, Λ, L)::Constant
               
-∂ₜ(ρ) == (ρ * (((1 - μ) + (Λ - 1) * ρ) - ρ * ρ) + 0.5 * (L * L - ρ) * Δ(ρ)) - 0.125 * ρ * Δ(ρ) * Δ(ρ)
source
Decapodes.Canon.Biology.turing_continuous_ringConstant

Turing Continuous Ring

Source

Model

(X, Y)::Form0
+∂ₜ(ρ) == (ρ * (((1 - μ) + (Λ - 1) * ρ) - ρ * ρ) + 0.5 * (L * L - ρ) * Δ(ρ)) - 0.125 * ρ * Δ(ρ) * Δ(ρ)
source
Decapodes.Canon.Biology.turing_continuous_ringConstant

Turing Continuous Ring

Source

Model

(X, Y)::Form0
               
 (μ, ν, a, b, c, d)::Constant
               
 ∂ₜ(X) == a * X + b * Y + μ * Δ(X)
               
-∂ₜ(Y) == c * X + d * Y + ν * Δ(X)
source

Environment

Decapodes.Canon.Environment.boundary_conditionsConstant

Boundary Conditions

Source

Model

(S, T)::Form0
+∂ₜ(Y) == c * X + d * Y + ν * Δ(X)
source

Environment

Decapodes.Canon.Environment.boundary_conditionsConstant

Boundary Conditions

Source

Model

(S, T)::Form0
               
 (Ṡ, T_up)::Form0
               
@@ -191,37 +191,37 @@
               
 Ṫ == ∂_spatial(T_up)
               
-v̇ == ∂_noslip(v_up)
source
Decapodes.Canon.Environment.energy_balanceConstant

Energy balance

Source

energy balance equation from Budyko Sellers

Model

(Tₛ, ASR, OLR, HT)::Form0
+v̇ == ∂_noslip(v_up)
source
Decapodes.Canon.Environment.energy_balanceConstant

Energy balance

Source

energy balance equation from Budyko Sellers

Model

(Tₛ, ASR, OLR, HT)::Form0
               
 C::Constant
               
 Tₛ̇ == ∂ₜ(Tₛ)
               
-Tₛ̇ == ((ASR - OLR) + HT) ./ C
source
Decapodes.Canon.Environment.equation_of_stateConstant

Equation of State

Source

Model

(b, T, S)::Form0
+Tₛ̇ == ((ASR - OLR) + HT) ./ C
source
Decapodes.Canon.Environment.equation_of_stateConstant

Equation of State

Source

Model

(b, T, S)::Form0
               
 (g, α, β)::Constant
               
-b == g * (α * T - β * S)
source
Decapodes.Canon.Environment.glenConstant

Glens Law

Source

Nye, J. F. (1957). The Distribution of Stress and Velocity in Glaciers and Ice-Sheets. Proceedings of the Royal Society of London. Series A, Mathematical and Physical Sciences, 239(1216), 113–133. http://www.jstor.org/stable/100184

Model

Γ::Form1
+b == g * (α * T - β * S)
source
Decapodes.Canon.Environment.glenConstant

Glens Law

Source

Nye, J. F. (1957). The Distribution of Stress and Velocity in Glaciers and Ice-Sheets. Proceedings of the Royal Society of London. Series A, Mathematical and Physical Sciences, 239(1216), 113–133. http://www.jstor.org/stable/100184

Model

Γ::Form1
               
 (A, ρ, g, n)::Constant
               
-Γ == (2 / (n + 2)) * A * (ρ * g) ^ n
source
Decapodes.Canon.Environment.halfar_eq2Constant

Halfar (Eq. 2)

Source

Halfar, P. (1981), On the dynamics of the ice sheets, J. Geophys. Res., 86(C11), 11065–11072, doi:10.1029/JC086iC11p11065

Model

h::Form0
+Γ == (2 / (n + 2)) * A * (ρ * g) ^ n
source
Decapodes.Canon.Environment.halfar_eq2Constant

Halfar (Eq. 2)

Source

Halfar, P. (1981), On the dynamics of the ice sheets, J. Geophys. Res., 86(C11), 11065–11072, doi:10.1029/JC086iC11p11065

Model

h::Form0
               
 Γ::Form1
               
 n::Constant
               
-∂ₜ(h) == (∘(⋆, d, ⋆))(((Γ * d(h)) ∧ mag(♯(d(h))) ^ (n - 1)) ∧ h ^ (n + 2))
source
Decapodes.Canon.Environment.insolationConstant

Insolation

Source

Model

Q::Form0
+∂ₜ(h) == (∘(⋆, d, ⋆))(((Γ * d(h)) ∧ mag(♯(d(h))) ^ (n - 1)) ∧ h ^ (n + 2))
source
Decapodes.Canon.Environment.insolationConstant

Insolation

Source

Model

Q::Form0
               
 cosϕᵖ::Constant
               
-Q == 450cosϕᵖ
source
Decapodes.Canon.Environment.tracerConstant

Tracer

Source

Model

(c, C, F, c_up)::Form0
+Q == 450cosϕᵖ
source
Decapodes.Canon.Environment.tracerConstant

Tracer

Source

Model

(c, C, F, c_up)::Form0
               
 (v, V, q)::Form1
               
-c_up == (((-1 * (⋆)(L(v, (⋆)(c))) - (⋆)(L(V, (⋆)(c)))) - (⋆)(L(v, (⋆)(C)))) - (∘(⋆, d, ⋆))(q)) + F
source
Decapodes.Canon.Environment.warmingConstant

Warming

Source

Model

Tₛ::Form0
+c_up == (((-1 * (⋆)(L(v, (⋆)(c))) - (⋆)(L(V, (⋆)(c)))) - (⋆)(L(v, (⋆)(C)))) - (∘(⋆, d, ⋆))(q)) + F
source
Decapodes.Canon.Environment.warmingConstant

Warming

Source

Model

Tₛ::Form0
               
 A::Form1
               
-A == avg₀₁(5.8282 * 10 ^ (-0.236Tₛ) * 1.65e7)
source
[ Info: Page built in 0 seconds.
-[ Info: This page was last built at 2024-06-13T15:06:26.883.
+A == avg₀₁(5.8282 * 10 ^ (-0.236Tₛ) * 1.65e7)source
[ Info: Page built in 0 seconds.
+[ Info: This page was last built at 2024-06-13T19:57:17.701.
diff --git a/dev/ch/cahn-hilliard/index.html b/dev/ch/cahn-hilliard/index.html index 6fd5f323..e8661818 100644 --- a/dev/ch/cahn-hilliard/index.html +++ b/dev/ch/cahn-hilliard/index.html @@ -28,8 +28,8 @@ constants = (D = 0.5, γ = 0.5); Colorbar(fig[1,2], msh)

"Initial conditions"

We'll now create the simulation code representing the Cahn-Hilliard equation. We pass nothing in the second argument to sim since we have no custom functions to pass in.

sim = eval(gensim(CahnHilliard))
-fₘ = sim(sd, nothing, DiagonalHodge());
(::Main.var"#f#3"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}}) (generic function with 1 method)

Getting the Solution

Now that everything is set up and ready, we can solve the equation. We run the simulation for 200 time units to see the long-term evolution of the fluid. Note we only save the solution at intervals of 0.1 time units in order to reduce the memory-footprint of the solve.

tₑ = 200
+fₘ = sim(sd, nothing, DiagonalHodge());
(::Main.var"#f#5"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}}) (generic function with 1 method)

Getting the Solution

Now that everything is set up and ready, we can solve the equation. We run the simulation for 200 time units to see the long-term evolution of the fluid. Note we only save the solution at intervals of 0.1 time units in order to reduce the memory-footprint of the solve.

tₑ = 200
 prob = ODEProblem(fₘ, u₀, (0, tₑ), constants)
 soln = solve(prob, Tsit5(), saveat=0.1);
-soln.retcode
ReturnCode.Success = 1

And we can see the result as a gif.

"CahnHilliardRes"

[ Info: Page built in 180 seconds.
-[ Info: This page was last built at 2024-06-13T15:09:27.054.
+soln.retcode
ReturnCode.Success = 1

And we can see the result as a gif.

"CahnHilliardRes"

[ Info: Page built in 184 seconds.
+[ Info: This page was last built at 2024-06-13T20:00:21.528.
diff --git a/dev/cism/cism/index.html b/dev/cism/cism/index.html index 1c79ef22..cc421013 100644 --- a/dev/cism/cism/index.html +++ b/dev/cism/cism/index.html @@ -184,7 +184,7 @@ ####################### sim = eval(gensim(ice_dynamics2D)) -fₘ = sim(sd, generate)
(::Main.var"#f#16"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#10#13"{Main.var"#8#11"}, Main.var"#10#13"{Main.var"#9#12"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Julia is a "Just-In-Time" compiled language. That means that functions are compiled the first time they are called, and later calls to those functions skip this step. To get a feel for just how fast this simulation is, we will run the dynamics twice, once for a very short timespan to trigger pre-compilation, and then again for the actual dynamics.

# Pre-compile simulation
+fₘ = sim(sd, generate)
(::Main.var"#f#18"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#10#13"{Main.var"#8#11"}, Main.var"#10#13"{Main.var"#9#12"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Julia is a "Just-In-Time" compiled language. That means that functions are compiled the first time they are called, and later calls to those functions skip this step. To get a feel for just how fast this simulation is, we will run the dynamics twice, once for a very short timespan to trigger pre-compilation, and then again for the actual dynamics.

# Pre-compile simulation
 
 # Julia will pre-compile the generated simulation the first time it is run.
 @info("Precompiling Solver")
@@ -204,14 +204,14 @@
 [ Info: Done

We can benchmark the compiled simulation with @benchmarkable. This macro runs many samples of the simulation function so we get an accurate estimate of the simulation time. The simulation time is quite fast compared to the CISM benchmarks. These results are run automatically via GitHub Actions as part of our docs build, which is not optimized for numerical simulations.

# Time the simulation
 
 b = @benchmarkable solve(prob, Tsit5(), saveat=0.1)
-c = run(b)
BenchmarkTools.Trial: 195 samples with 1 evaluation.
- Range (minmax):  22.932 ms64.142 ms   GC (min … max): 0.00% … 14.43%
- Time  (median):     23.157 ms               GC (median):    0.00%
- Time  (mean ± σ):   25.281 ms ±  6.025 ms   GC (mean ± σ):  1.95% ±  5.19%
+c = run(b)
BenchmarkTools.Trial: 203 samples with 1 evaluation.
+ Range (minmax):  22.936 ms45.499 ms   GC (min … max): 0.00% … 31.99%
+ Time  (median):     23.290 ms               GC (median):    0.00%
+ Time  (mean ± σ):   24.451 ms ±  3.428 ms   GC (mean ± σ):  2.14% ±  5.89%
 
-  █                                                           
-  █▄▄▁▁▁▁▁▁▄▁▁▆▆▄▆▁▁▁▁▁▁▁▁▁▁▄▁▁▁▁▁▁▁▁▁▁▁▄▁▅▆▆▁▄▄▁▁▁▁▁▁▁▁▁▁▅ ▄
-  22.9 ms      Histogram: log(frequency) by time        47 ms <
+  ▇                                                           
+  ██▁▄▁▄▅▁▄▁▄▁▁▁▄▄▁▁▄▁▄▁▄▁▆▄▇▅▄▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▄▁▁▁▁▁▁▁▄ ▅
+  22.9 ms      Histogram: log(frequency) by time      39.7 ms <
 
  Memory estimate: 48.33 MiB, allocs estimate: 3878.

Here we save the solution information to a file.

@save "ice_dynamics2D.jld2" soln

We recall that these dynamics are of the "shallow slope" and "shallow ice" approximations. So, at the edge of our parabolic dome of ice, we expect increased error as the slope increases. On the interior of the dome, we expect the dynamics to match more closely that given by the analytic model. We will see that the CISM results likewise accumulate error in the same neighborhood.

"Halfar Small Ice Approximation Quote"

# Plot the final conditions
 function plot_final_conditions()
@@ -270,5 +270,5 @@
   record(fig, "ice_dynamics_cism.gif", range(0.0, tₑ; length=frames); framerate = 30) do t
     msh.color = soln(t).dynamics_h
   end
-end
"ice_dynamics_cism.gif"

"Ice Dynamics

For comparison's sake, we paste the results produced by CISM below. We observe that the error likewise accumulates around the edge of the dome, with more accurate predictions on the interior. We note that our simulation produces slight over-estimates on the interior, but there are further strategies that one can employ to increase accuracy, such as tweaking the error tolerance of the solver, and so on.

Not that since the DEC is based on triangulated meshes, the "resolution" of the CISM benchmark and the Decapodes implementation cannot be directly compared. An advantage of the DEC is that we do not need to operate on uniform grids. For example, you could construct a mesh that is finer along the dome edge, where you need more resolution, and coarser as you are farther away from the reach of the ice.

CISM Results

We saw in this document how to create performant and accurate simulations in the Decapodes framework, and compared against the CISM library . Although we do not expect to be both more performant and accurate compared to every hand-crafted simulation, Decapodes makes up for this difference in terms of development time, flexibility, and composition. For example, the original implementation of the Decapodes shallow ice model took place over a couple of afternoons.

Since Decapodes targets high-level representations of physics, it is uniquely suited to incorporating knowledge from subject matter experts to increase simulation accuracy. This process does not require an ice dynamics expert to edit physics equations that have already been weaved into solver code.

Further improvements to the Decapodes library are made continuously. We are creating implementations of DEC operators that are constructed and execute faster. And we are in the beginning stages of 3D simulations using the DEC.

[ Info: Page built in 31 seconds.
-[ Info: This page was last built at 2024-06-13T15:09:57.615.
+end
"ice_dynamics_cism.gif"

"Ice Dynamics

For comparison's sake, we paste the results produced by CISM below. We observe that the error likewise accumulates around the edge of the dome, with more accurate predictions on the interior. We note that our simulation produces slight over-estimates on the interior, but there are further strategies that one can employ to increase accuracy, such as tweaking the error tolerance of the solver, and so on.

Not that since the DEC is based on triangulated meshes, the "resolution" of the CISM benchmark and the Decapodes implementation cannot be directly compared. An advantage of the DEC is that we do not need to operate on uniform grids. For example, you could construct a mesh that is finer along the dome edge, where you need more resolution, and coarser as you are farther away from the reach of the ice.

CISM Results

We saw in this document how to create performant and accurate simulations in the Decapodes framework, and compared against the CISM library . Although we do not expect to be both more performant and accurate compared to every hand-crafted simulation, Decapodes makes up for this difference in terms of development time, flexibility, and composition. For example, the original implementation of the Decapodes shallow ice model took place over a couple of afternoons.

Since Decapodes targets high-level representations of physics, it is uniquely suited to incorporating knowledge from subject matter experts to increase simulation accuracy. This process does not require an ice dynamics expert to edit physics equations that have already been weaved into solver code.

Further improvements to the Decapodes library are made continuously. We are creating implementations of DEC operators that are constructed and execute faster. And we are in the beginning stages of 3D simulations using the DEC.

[ Info: Page built in 32 seconds.
+[ Info: This page was last built at 2024-06-13T20:00:53.223.
diff --git a/dev/cism/ice_dynamics2D.jld2 b/dev/cism/ice_dynamics2D.jld2 index 1b0ef2a8e08431d65c168f3651315347eb764a17..73d7b2b207606bcee1fdf04b56d5b9f2e5e881a8 100644 GIT binary patch delta 2152 zcmW;MV{{OD7=ZD!w54U+ww7(%wr#g+)v9G<*|jZm*|xj3cG>lPp8MhVoc}rR`}O_D zMZOP?LsWE7KuBmzP*_Y*0OBD&5+ETGArQ_JBMFis8ImIyDUcGWkQ!-_7U_^48ITc~ zkQrH!71@v-Igk^%kQ;fB7x|DM1yB%$P#8r}6va>+B~TKjP#R@W7UfVL6%c}osD#R> zf~u&7>ZpO5sD;|7gSx1P`e=ZLXoSXSf~IJO=4gSIXoc2jgSKdg_UM3)=!DMbg0AR> z?&yJ@=!M?sgTCm8{uqFP7=*zXf}t3O;TVCD7=_UogRvNg@tA;#n1sogf~lB>>6n3; zn1xWx#vIH=80KL<79bo8u?UM1fhAaqWmt|CScz3wjWt+{by$xL*oaNoj7V(3R&2v| z?7&X!!fx!rUhKnu9Kb;wLKLDAgTpw2qd11+IDwNmh0{2Lvp9!XoW})R#3fwD6 z@fE(tH~1Fc;d}gmIQ)p8@H2kFulNn`@jL#&pZE)Z;~)GR&h1CQ#53_t0+Y}rGJ)p* zy~Mtfn4~6|Np6Bo3X{^LGO0}(lh&j&=}iWc(PT22O%{{YWHZ@K4wKX5GPzA2lh@=k z`Aq>+&=fL-O%YSn6f?z52~*OPGNnx!Q`VF-Vk(+Srn0GGs+wx1x~XAmnp&o| zsblJzdZxZcf6rm<;anwn;&xoKfqnpUQ@X=B=&cBZ}QU^<#krnBi{x|(jLyXj$i znqH>2>0|nuex|<}UG3Ky2Vvd?)=D0awPMTBZ zv^itWnsX-BoHrNDMRUnqHdoA5bIn{gH_T0Q%iK11%w2QO+&2%*L-WWyHc!k`^UORq UFU(8x%Dgsj0%G~x0%8OI1MUb&lK=n! delta 2152 zcmW;MV{{OD7=ZD!w54U+b}iesZQI7GRjZbbW!JXMW!vuB+GW@GdG3ebbN=VN@7MPq z7x_Lo4pGrT0U@C=L18gL0f>kANPvV$gg`h?j3h{kWJr!+q(DlfLTaQzTBJjIWI#q_ zLS|$^R%AnVZ1V~q7fRS37VoAnxh3;q7_=B4cej|+M@$Hq7yo!3%a5k zx}yhrq8ECj5Bj1X`eOhFVh{#n2!>)9hGPUqViZPW48~#{#$y5|ViG1}3Z`Njreg+X zVirO%8*?xhVVH;cSb%UW#3C$41eRbamSH(oU?o;zHP&D))?qz1U?VnRGa|7CTd@t> zu>(7?3%jugd$AAuaR3K#2vLYe3=ZQ6j^Y@O;{;CP6i(v|&f*+maUK_N5tncoS8x^A za2+>r6Sr_1cW@W?a32rw5RdQ}Pw*7a@EkAj60h(YZ}1lH@Bu!=NB9_@;8T2t&+!Gm z#8>zl-{4z(hwt$N;_xGW!q4~xzv4H%$M5(9f8sCvjeqcOIJX}G6VJpq2~0wh$OM}I z_Y(U`Vv?FZXRNX=<6; zrjDs=>Y4hcfoW(OnZ~AxX=<98=B9;dX1w)}?xu(7 zX?mI7rjO}s`kDS_fEj28nZag=8ES@^;bw#xX-1jRW{eqY#+mVEf|+P0naO5~nQEq) z>1KwRX=a&FGuzBDb4{3;XXcv)CfqDEi_BsZVV0PsW|>)TR+yD$m04}pn6+k|S#LI& zjb@YCY$DATv(;=f+szKM)9f<4%^tJY>@)k#0dvqCGEpYl#F)e8h&gJGnd9b!IcZLr z)8>phYtETibKYDq7tJMe*<3MK%{6n~+%PxIEpyx4F?Y>9bKg8L56vU<*gP>$%`@}d Uyf81#EA!gC35ex$3y2N;58Y5mkpKVy diff --git a/dev/equations/equations/index.html b/dev/equations/equations/index.html index c989fece..7d681b76 100644 --- a/dev/equations/equations/index.html +++ b/dev/equations/equations/index.html @@ -992,4 +992,4 @@ '/>

Often you will have a linear material where you are scaling by a constant, and a nonlinear version of that material where that scaling is replaced by a generic nonlinear function. This is why we allow Decapodes to represent both of these types of equations.

[ Info: Page built in 1 second.
-[ Info: This page was last built at 2024-06-13T15:09:58.850.
+[ Info: This page was last built at 2024-06-13T20:00:54.480. diff --git a/dev/grigoriev/grigoriev.jld2 b/dev/grigoriev/grigoriev.jld2 index e4df941d65cd22c2e0f850b583d7c63d980e456e..4b6daa0e8ca33f2a426e746f2944dfd024905a06 100644 GIT binary patch delta 670 zcmWm3XIl&a007{pbX<$9iiF5Y(Uh4`M)qi#-8Do~nGGSw6<1`FQuqUX>o4^uJdf9h z_tQHc8xPDg8w$piM#912a4?Q|7LY)Ig)HL#Cz3=mDWtNPC8V*GWh`d}D@iAVOjfa) zEY^_CTGp|i95%3#TsEx;wRJ0@QdGb;ozU>-+!4#)u;df delta 670 zcmWm3XFC)C007{Jy1Gbd8Hv&!h)YUJD%yjF(U3c@HD zYsp|8nPjn^4P>*CO>AZhTiM2TcCeFOAJm4XZc+3-?@{H%S^MVdK>Eb2byy7))=;1BB^zn`;{R}Y3dtwYR%m+R) j!Y4-g%oo1$jqi*x&ICXB$t1s+Vw&H~%teBKqJRGZlp@up diff --git a/dev/grigoriev/grigoriev/index.html b/dev/grigoriev/grigoriev/index.html index 21332aa2..6548b115 100644 --- a/dev/grigoriev/grigoriev/index.html +++ b/dev/grigoriev/grigoriev/index.html @@ -117,7 +117,7 @@ end return op end
generate (generic function with 1 method)

Generate simulation

sim = eval(gensim(ice_dynamics, dimension=2))
-fₘ = sim(sd, generate)
(::Main.var"#f#12"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#6#8", Main.var"#7#9"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Run

tₑ = 10
+fₘ = sim(sd, generate)
(::Main.var"#f#14"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#6#8", Main.var"#7#9"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Run

tₑ = 10
 
 @info("Solving Grigoriev Ice Cap")
 prob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)
@@ -127,5 +127,5 @@
 
 @save "grigoriev.jld2" soln
[ Info: Solving Grigoriev Ice Cap
 soln.retcode = SciMLBase.ReturnCode.Success
-[ Info: Done

Results and Discussion

We observe the usual Halfar model phenomena of ice "melting". Note that since the "shallow slope" approximation does not hold on the boundaries (due to the so-called "ice cliffs" described in the Van Tricht et al. paper), we do not expect the "creep" effect to be physical in this region of the domain. Rather, the Halfar model's predictive power is tuned for the interiors of ice caps and glaciers. Note that we also assume here that the bedrock that the ice rests on is flat. We may in further documents demonstrate how to use topographic data from Digital Elevation Models to inform the elevation of points in the mesh itself.

Grigoriev_ICs

Grigoriev_FCs

Grigoriev_Dynamics

[ Info: Page built in 33 seconds.
-[ Info: This page was last built at 2024-06-13T15:10:32.068.
+[ Info: Done

Results and Discussion

We observe the usual Halfar model phenomena of ice "melting". Note that since the "shallow slope" approximation does not hold on the boundaries (due to the so-called "ice cliffs" described in the Van Tricht et al. paper), we do not expect the "creep" effect to be physical in this region of the domain. Rather, the Halfar model's predictive power is tuned for the interiors of ice caps and glaciers. Note that we also assume here that the bedrock that the ice rests on is flat. We may in further documents demonstrate how to use topographic data from Digital Elevation Models to inform the elevation of points in the mesh itself.

Grigoriev_ICs

Grigoriev_FCs

Grigoriev_Dynamics

[ Info: Page built in 34 seconds.
+[ Info: This page was last built at 2024-06-13T20:01:28.379.
diff --git a/dev/halmo/halmo/index.html b/dev/halmo/halmo/index.html index fa24102d..14a75d0b 100644 --- a/dev/halmo/halmo/index.html +++ b/dev/halmo/halmo/index.html @@ -275,7 +275,7 @@ Open(eq10forN2, [:𝐮, :w]), Open(blocking, [:h, :𝐮, :w])])) -to_graphviz(ice_dynamics, verbose=false)Example block output

We can now generate our simulation:

sim = eval(gensim(ice_water))
simulate (generic function with 2 methods)

Meshes and Initial Conditions

Since we want to demonstrate these physics on the Earth, we will use one of our icosphere discretizations with the appropriate radius.

rₑ = 6378e3 # [km]
+to_graphviz(ice_dynamics, verbose=false)
Example block output

We can now generate our simulation:

sim = eval(gensim(ice_water))
#1 (generic function with 2 methods)

Meshes and Initial Conditions

Since we want to demonstrate these physics on the Earth, we will use one of our icosphere discretizations with the appropriate radius.

rₑ = 6378e3 # [km]
 s = loadmesh(Icosphere(5, rₑ))
 sd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)
 subdivide_duals!(sd, Barycenter())
@@ -289,7 +289,7 @@
     _ => default_dec_matrix_generate(sd, my_symbol, hodge)
   end
   return op
-end;
generate (generic function with 1 method)

Let's combine our mesh with our physics to instantiate our simulation:

fₘ = sim(sd, generate);
(::Main.var"#f#7"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int32}, CombinatorialSpaces.FastDEC.var"#19#20"{SparseArrays.SparseMatrixCSC{Float64, Int64}}, CombinatorialSpaces.FastDEC.var"#17#18"{SparseArrays.SparseMatrixCSC{Float64, Int64}}, CombinatorialSpaces.FastDEC.var"#32#33"{LinearAlgebra.Diagonal{Float64, Vector{Float64}}, CombinatorialSpaces.FastDEC.var"#21#22"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex2D{Bool, Float64, GeometryBasics.Point{3, Float64}}, CombinatorialSpaces.FastDEC.var"#7#8"{Tuple{Matrix{Int32}, Matrix{Float64}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Float64, Int64}}, CombinatorialSpaces.FastDEC.var"#30#31"{SparseArrays.UMFPACK.UmfpackLU{Float64, Int32}}}, typeof(Main.sigmoid), Main.var"#9#10", Decapodes.var"#31#32"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Int8, Int32}, Decapodes.var"#7#9"{SparseArrays.UMFPACK.UmfpackLU{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

We can now supply initial conditions:

ice_thickness = map(sd[:point]) do (_,_,z)
+end;
generate (generic function with 1 method)

Let's combine our mesh with our physics to instantiate our simulation:

fₘ = sim(sd, generate);
(::Main.var"#f#9"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int32}, SparseArrays.SparseMatrixCSC{Float64, Int32}, CombinatorialSpaces.FastDEC.var"#19#20"{SparseArrays.SparseMatrixCSC{Float64, Int64}}, CombinatorialSpaces.FastDEC.var"#17#18"{SparseArrays.SparseMatrixCSC{Float64, Int64}}, CombinatorialSpaces.FastDEC.var"#32#33"{LinearAlgebra.Diagonal{Float64, Vector{Float64}}, CombinatorialSpaces.FastDEC.var"#21#22"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex2D{Bool, Float64, GeometryBasics.Point{3, Float64}}, CombinatorialSpaces.FastDEC.var"#7#8"{Tuple{Matrix{Int32}, Matrix{Float64}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Float64, Int64}}, CombinatorialSpaces.FastDEC.var"#30#31"{SparseArrays.UMFPACK.UmfpackLU{Float64, Int32}}}, typeof(Main.sigmoid), Main.var"#11#12", Decapodes.var"#31#32"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}, Decapodes.var"#19#21"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, SparseArrays.SparseMatrixCSC{Int8, Int32}, Decapodes.var"#7#9"{SparseArrays.UMFPACK.UmfpackLU{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

We can now supply initial conditions:

ice_thickness = map(sd[:point]) do (_,_,z)
   z < 0.8*rₑ ? 0 : 1
 end
 
@@ -336,5 +336,5 @@
   record(fig, "halmo_ice.gif", range(0.0, tₑ; length=frames); framerate = 20) do t
     msh.color = soln(t).ice_thickness
   end
-end
"halmo_ice.gif"

HalfarMohamedIce

[ Info: Page built in 42 seconds.
-[ Info: This page was last built at 2024-06-13T15:11:14.545.
+end
"halmo_ice.gif"

HalfarMohamedIce

[ Info: Page built in 44 seconds.
+[ Info: This page was last built at 2024-06-13T20:02:12.728.
diff --git a/dev/ice_dynamics/ice_dynamics/index.html b/dev/ice_dynamics/ice_dynamics/index.html index f7e9ac76..8ba713ff 100644 --- a/dev/ice_dynamics/ice_dynamics/index.html +++ b/dev/ice_dynamics/ice_dynamics/index.html @@ -159,7 +159,7 @@ end return (args...) -> op(args...) end
generate (generic function with 1 method)

Generate the simulation

Now, we have everything we need to generate our simulation:

sim = eval(gensim(ice_dynamics1D, dimension=1))
-fₘ = sim(sd, generate)
(::Main.var"#f#26"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, SparseArrays.SparseMatrixCSC{Float64, Int64}, Main.var"#14#23"{Main.var"#13#22"}, Main.var"#14#23"{Main.var"#6#15"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex1D{Bool, Float64, GeometryBasics.Point{2, Float64}}}}, Decapodes.var"#37#38"{SparseArrays.SparseMatrixCSC{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Pre-compile and run

The first time that you run a function, Julia will pre-compile it, so that later runs will be fast. We'll solve our simulation for a short time span, to trigger this pre-compilation, and then run it.

@info("Precompiling Solver")
+fₘ = sim(sd, generate)
(::Main.var"#f#28"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 10}}}, SparseArrays.SparseMatrixCSC{Float64, Int64}, Main.var"#14#23"{Main.var"#13#22"}, Main.var"#14#23"{Main.var"#6#15"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex1D{Bool, Float64, GeometryBasics.Point{2, Float64}}}}, Decapodes.var"#37#38"{SparseArrays.SparseMatrixCSC{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Pre-compile and run

The first time that you run a function, Julia will pre-compile it, so that later runs will be fast. We'll solve our simulation for a short time span, to trigger this pre-compilation, and then run it.

@info("Precompiling Solver")
 prob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)
 soln = solve(prob, Tsit5())
 soln.retcode != :Unstable || error("Solver was not stable")
@@ -233,7 +233,7 @@
   end
   return (args...) -> op(args...)
 end
generate (generic function with 1 method)

Generate simulation

sim = eval(gensim(ice_dynamics2D, dimension=2))
-fₘ = sim(sd, generate)
(::Main.var"#f#46"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#40#43"{Main.var"#39#42"}, Main.var"#40#43"{Main.var"#38#41"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}}, Decapodes.var"#37#38"{SparseArrays.SparseMatrixCSC{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Pre-compile and run 2D

@info("Precompiling Solver")
+fₘ = sim(sd, generate)
(::Main.var"#f#50"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#42#45"{Main.var"#41#44"}, Main.var"#42#45"{Main.var"#40#43"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}}, Decapodes.var"#37#38"{SparseArrays.SparseMatrixCSC{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

Pre-compile and run 2D

@info("Precompiling Solver")
 # We run for a short timespan to pre-compile.
 prob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)
 soln = solve(prob, Tsit5())
@@ -285,7 +285,7 @@
   stress_ρ = ρ,
   stress_g = g,
   stress_A = A)
(n = 3, stress_ρ = 910, stress_g = 9.8, stress_A = 1.0e-16)
sim = eval(gensim(ice_dynamics2D, dimension=2))
-fₘ = sim(sd, generate)
(::Main.var"#f#53"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#40#43"{Main.var"#39#42"}, Main.var"#40#43"{Main.var"#38#41"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}}, Decapodes.var"#37#38"{SparseArrays.SparseMatrixCSC{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

For brevity's sake, we'll skip the pre-compilation cell.

tₑ = 5e25
+fₘ = sim(sd, generate)
(::Main.var"#f#59"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#42#45"{Main.var"#41#44"}, Main.var"#42#45"{Main.var"#40#43"{SparseArrays.SparseMatrixCSC{GeometryBasics.Point{3, Float64}, Int64}}}, Decapodes.var"#37#38"{SparseArrays.SparseMatrixCSC{Float64, Int32}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

For brevity's sake, we'll skip the pre-compilation cell.

tₑ = 5e25
 
 @info("Solving")
 prob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)
@@ -310,5 +310,5 @@
   record(fig, "ice_dynamics2D_sphere.gif", range(0.0, tₑ/64; length=frames); framerate = 20) do t
     msh.color = soln(t).dynamics_h
   end
-end
"ice_dynamics2D_sphere.gif"

IceDynamics2DSphere

[ Info: Page built in 47 seconds.
-[ Info: This page was last built at 2024-06-13T15:12:01.885.
+end
"ice_dynamics2D_sphere.gif"

IceDynamics2DSphere

[ Info: Page built in 48 seconds.
+[ Info: This page was last built at 2024-06-13T20:03:00.986.
diff --git a/dev/ice_dynamics/ice_dynamics1D.jld2 b/dev/ice_dynamics/ice_dynamics1D.jld2 index c5339b6e219743fd846effe7f38107154891886a..a0fda14f33168034e0d6828b0c746f0953d9a354 100644 GIT binary patch delta 24 gcmZ2?m}lK#o(-MWj26vZ*6m%^jN7}cnN*$u0D@)-#Q*>R delta 24 gcmZ2?m}lK#o(-MWjAqSU*6m%^jN7}cnN*$u0D?*h!vFvP diff --git a/dev/ice_dynamics/ice_dynamics2D.jld2 b/dev/ice_dynamics/ice_dynamics2D.jld2 index 5a3090f3daaa6c57e15140a3ced075532c5bcda3..7fa07c955527a8df575b196c41b7f6109b62c100 100644 GIT binary patch delta 103 zcmeA;uHA85d&4p|Rucnd6XVIN*?d@xlub-0|7QEjX==a#25OTHIdoY~z~b5*#;hiW o$|few$sFy;9E?EB1jNih%mT!$K+Fcj>_7~X=iHvm!KHc|0G**4S^xk5 delta 102 zcmeA;uHA85d&4p|R$~ig6T``?*?d?Glue8$|7QEjX=26z25OTHIrJdH+8oBL#+J$^ mM$O3_?a3UBK+FWh%s|Wn#H>Kf2E^<@43g*Ep3K3edK& -Decapodes.jl · Decapodes.jl

Decapodes.jl

Decapodes.jl is a framework for developing, composing, and simulating physical systems.

Decapodes.jl is the synthesis of Applied Category Theory (ACT) techniques for formalizing and composing physics equations, and Discrete Exterior Calculus (DEC) techniques for formalizing differential operators. CombinatorialSpaces.jl hosts tools for discretizing space and defining DEC operators on simplicial complexes, and DiagrammaticEquations.jl hosts tooling for representing the equations as formal ACT diagrams. This repository combines these two packages, compiling diagrams down to runnable simulation code.

By combining the power of ACT and the DEC, we seek to improve the scientific computing workflow. Decapodes simulations are hierarchically composable, generalize over any type of manifold, and are performant and accurate with a declarative domain specific language (DSL) that is human-readable.

Grigoriev Ice Cap Dynamics

Getting started

Walkthroughs creating, composing, and solving Decapodes are available in the side-bar of this documentation page. Further example scripts are available in the examples folder of the Decapodes.jl GitHub repo, and will be added to this documentation site soon.

Under Active Development

This library is currently under active development, and so is not yet at a point where a constant API/behavior can be assumed. That being said, if this project looks interesting/relevant please contact us and let us know!

+Decapodes.jl · Decapodes.jl

Decapodes.jl

Decapodes.jl is a framework for developing, composing, and simulating physical systems.

Decapodes.jl is the synthesis of Applied Category Theory (ACT) techniques for formalizing and composing physics equations, and Discrete Exterior Calculus (DEC) techniques for formalizing differential operators. CombinatorialSpaces.jl hosts tools for discretizing space and defining DEC operators on simplicial complexes, and DiagrammaticEquations.jl hosts tooling for representing the equations as formal ACT diagrams. This repository combines these two packages, compiling diagrams down to runnable simulation code.

By combining the power of ACT and the DEC, we seek to improve the scientific computing workflow. Decapodes simulations are hierarchically composable, generalize over any type of manifold, and are performant and accurate with a declarative domain specific language (DSL) that is human-readable.

Grigoriev Ice Cap Dynamics

Getting started

Walkthroughs creating, composing, and solving Decapodes are available in the side-bar of this documentation page. Further example scripts are available in the examples folder of the Decapodes.jl GitHub repo, and will be added to this documentation site soon.

Under Active Development

This library is currently under active development, and so is not yet at a point where a constant API/behavior can be assumed. That being said, if this project looks interesting/relevant please contact us and let us know!

diff --git a/dev/klausmeier/klausmeier/index.html b/dev/klausmeier/klausmeier/index.html index 9fa8df5c..87ff2486 100644 --- a/dev/klausmeier/klausmeier/index.html +++ b/dev/klausmeier/klausmeier/index.html @@ -100,7 +100,7 @@ [Open(Phytodynamics, [:n, :w]), Open(Hydrodynamics, [:n, :w])]) Klausmeier = apex(klausmeier_cospan) -to_graphviz(Klausmeier)Example block output

With our model now explicitly represented, we have everything we need to automatically generate simulation code. We could write this to an intermediate file and use it later, or we can go ahead and evaluate the code in this session.

sim = eval(gensim(Klausmeier, dimension=1))
simulate (generic function with 2 methods)

We now need a mesh to define our domain. In the 2D case, our CombinatorialSpaces library can read in arbitrary .OBJ files. In 1D, it is often simpler to just generate a mesh on the fly. Since we are running our physics on a circle - i.e periodic boundaries - we will use a simple function that generates it.

We will visualize the mesh embedded in two dimensions here, but in later visualizations, we can represent it as a periodic line.

# Define Mesh
+to_graphviz(Klausmeier)
Example block output

With our model now explicitly represented, we have everything we need to automatically generate simulation code. We could write this to an intermediate file and use it later, or we can go ahead and evaluate the code in this session.

sim = eval(gensim(Klausmeier, dimension=1))
#1 (generic function with 2 methods)

We now need a mesh to define our domain. In the 2D case, our CombinatorialSpaces library can read in arbitrary .OBJ files. In 1D, it is often simpler to just generate a mesh on the fly. Since we are running our physics on a circle - i.e periodic boundaries - we will use a simple function that generates it.

We will visualize the mesh embedded in two dimensions here, but in later visualizations, we can represent it as a periodic line.

# Define Mesh
 function circle(n, c)
   s = EmbeddedDeltaSet1D{Bool, Point2D}()
   map(range(0, 2pi - (pi/(2^(n-1))); step=pi/(2^(n-1)))) do t
@@ -124,7 +124,7 @@
   end
   return (args...) -> op(args...)
 end
generate (generic function with 1 method)

Let's pass our mesh and methods of generating operators to our simulation code.

# Instantiate Simulation
-fₘ = sim(sd, generate, DiagonalHodge())
(::Main.var"#f#3"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#8#10"{Main.var"#7#9"}, Decapodes.var"#23#25"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, LinearAlgebra.Diagonal{Float64, Vector{Float64}}}) (generic function with 1 method)

With our simulation now ready, let's specify initial data to pass to it. We'll define them with plain Julia code.

The most interesting parameter here is our "downhill gradient" dX. This parameter defines how steep our slope is. Since our mesh is a circle, and we are setting dX to a constant value, this means that "downhill" always points counter-clockwise. Essentially, this is an elegant way of encoding an infinite hill.

# Define Initial Conditions
+fₘ = sim(sd, generate, DiagonalHodge())
(::Main.var"#f#5"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, SparseArrays.SparseMatrixCSC{Float64, Int32}, Main.var"#10#12"{Main.var"#9#11"}, Decapodes.var"#23#25"{1, Tuple{Matrix{Int32}, UnitRange{Int64}}}, LinearAlgebra.Diagonal{Float64, Vector{Float64}}}) (generic function with 1 method)

With our simulation now ready, let's specify initial data to pass to it. We'll define them with plain Julia code.

The most interesting parameter here is our "downhill gradient" dX. This parameter defines how steep our slope is. Since our mesh is a circle, and we are setting dX to a constant value, this means that "downhill" always points counter-clockwise. Essentially, this is an elegant way of encoding an infinite hill.

# Define Initial Conditions
 n_dist = Normal(pi)
 n = [pdf(n_dist, t)*(√(2pi))*7.2 + 0.08 - 5e-2 for t in range(0,2pi; length=ne(sd))]
 
@@ -158,4 +158,4 @@
   end
 end
 save_dynamics(:N, 20, "klausmeier.gif")
"klausmeier.gif"

Klausmeier

We can observe a few interesting phenomena that we wanted to capture:

  • The vegetation density bands move uphill in traveling waves.
  • The leading edge of the waves is denser than the rest of the band.
  • Over time, the periodicity of the vegetation bands stabilizes.
  • The distribution naturally emerges, despite the initial distribution is a simple normal distribution.
    • This is evidence against the real-world theory that these vegetation contours are the result of an initial (man-made) distribution.

Conclusion

Due to the ease of composition of Decapodes, representing the Klausmeier model opens up many areas for future work. For example, we can now compose these dynamics with a model of temperature dynamics informed by the Budyko-Sellers model. We can take advantage of the fact that the Lie derivative generalizes partial derivatives, and model the flow of water according to any vector field. Or, we can extend this model by composing it with a model that can recreate the so-called "leopard pattern" of vegetation, such as an "Interaction-Dispersion" model of vegetation dynamics given by Lejeune et al[4].

References

[1] W. A. Macfadyen, “Vegetation Patterns in the Semi-Desert Plains of British Somaliland,” The Geographical Journal, vol. 116, no. 4/6, p. 199, Oct. 1950, doi: 10.2307/1789384.

[2] C. A. Klausmeier, “Regular and Irregular Patterns in Semiarid Vegetation,” Science, vol. 284, no. 5421, pp. 1826–1828, Jun. 1999, doi: 10.1126/science.284.5421.1826.

[3] W. A. Macfadyen, “Soil and Vegetation in British Somaliland,” Nature, vol. 165, no. 4186, Art. no. 4186, Jan. 1950, doi: 10.1038/165121a0.

[4] O. Lejeune and M. Tlidi, “A Model for the Explanation of Vegetation Stripes (Tiger Bush),” Journal of Vegetation Science, vol. 10, no. 2, pp. 201–208, 1999, doi: 10.2307/3237141.

[ Info: Page built in 25 seconds.
-[ Info: This page was last built at 2024-06-13T15:12:26.967.
+[ Info: This page was last built at 2024-06-13T20:03:25.984. diff --git a/dev/navier_stokes/ns/index.html b/dev/navier_stokes/ns/index.html index 64bd71ec..f306f718 100644 --- a/dev/navier_stokes/ns/index.html +++ b/dev/navier_stokes/ns/index.html @@ -61,4 +61,4 @@ gcd = great_circle_dist(pnt,cntr) p.τ / (cosh(3gcd/p.a)^2) end

Based on the configuration, you can see different results that match the expected solutions from the literature.

Here is one set of results from using the inviscid Poisson formulation:

Vorticity

These vortices should be stable so we should see the same periodic function for both lines here. The difference between the lines is the accumulated error.

Azimuth Profile

[ Info: Page built in 0 seconds.
-[ Info: This page was last built at 2024-06-13T15:12:27.007.
+[ Info: This page was last built at 2024-06-13T20:03:26.024. diff --git a/dev/nhs/nhs_lite/index.html b/dev/nhs/nhs_lite/index.html index 68d0fe74..5e836da5 100644 --- a/dev/nhs/nhs_lite/index.html +++ b/dev/nhs/nhs_lite/index.html @@ -504,5 +504,5 @@ Downloads.download("https://cise.ufl.edu/~luke.morris/torus.obj", "torus.obj") s = EmbeddedDeltaSet2D("torus.obj") sd = EmbeddedDeltaDualComplex2D{Bool,Float64,Point3D}(s) -subdivide_duals!(sd, Barycenter())

"NHS_torus"

Results

In the DEC, vorticity is encoded with d⋆, and speed can be encoded with norm ♯. We can use our operators from CombinatorialSpaces.jl to create our GIFs.

Vorticity

Speed

[ Info: Page built in 7 seconds.
-[ Info: This page was last built at 2024-06-13T15:12:33.693.
+subdivide_duals!(sd, Barycenter())

"NHS_torus"

Results

In the DEC, vorticity is encoded with d⋆, and speed can be encoded with norm ♯. We can use our operators from CombinatorialSpaces.jl to create our GIFs.

Vorticity

Speed

[ Info: Page built in 6 seconds.
+[ Info: This page was last built at 2024-06-13T20:03:32.375.
diff --git a/dev/objects.inv b/dev/objects.inv index 56088bed0261fc6ab406554956b3677a1a835fbe..b0e3e51d417bfad4014cf12d051da75214ecd0e9 100644 GIT binary patch delta 2326 zcmV+x3F-Fo5&RO6e}BDNQFGfi5`M?8z|5WJUJA2yZZ0! zf-v$nWv~-Fw13ulvaYhjqg-iL6in$=PKyog?7`rm1Yfz=Wa(d%_wF@U~=F_AL#D^(>Ievt)Tvtry4|M({+=$%w$Re+epmYAH8 zMp<2w6G;FzLSjG$O`_{yRf~6axe4EjJy%kcOqjtjwP_@n)*WfhhjFJH@B#d%o&SjS z9N9<(4}TTU$Q?^_DjL}YrheZ8cgy5r00R?tOgJ#1&sk+5}3|}nJ zwJdkAYikYxdmuLzRa6+pG}&`XT%cq;C3ck5{M0X zT~!#qgN^%;AN0vSo#tmaT-tWC`DEhC zN`IY6v7HTv*|43ht?DSQ@7<}rN+m=S7)Qx6zPp^-8JZwHE54CqRRg7Hq@CDYGvNlYch3VK|zgE-aTe*p1@r%9=v%nR${B2-8G+ ziDNLX!6@WoJz2?@WC@G1*$1jFbOzhg@-VUrkM$M1#-hsY(|~dYSK?5S0+z-GAJyc) z%ZK|JapG`L^|8gZPPA#D>~Y;FS8$+}wMV0C#oEO3A#6ve-c!?V!{fMHo~Xb6pnv3^ zH!^1>l#}s@4&$w=oADu4*yUP*Ki^$6xxp^XBA`-MwmjL~X|?bx$QiO_+zbc4xMy@l z(IQI?o+#u0lZGEP33Qpx>R~eF>S`}|gaH1QH?P#<-lxtaNVkFsABJJ_p=n`r; zHWCLo`dCe?Sp*!*ND8*|!f&Xm#DBa#CL(bA3Ix8R5ouAK7S9p&-!S8XSsSWO5d}Y6diU_A6$fP3UJsW-$IUeb(tC6@xqD#Tco&qvr z=#}yT<8{y*yS??y;cTPg)X+rk|0L|gJFc_|d2RxiMPY{uc@Fg#s{j7?#S{9M?95F1VpWpIHiJfLzle ztw0erIrBX&PN(swLwhuE2;3lWgg{H~Pg_u>w@la*IYr?Vm#&OD=fOEsF%7PnQ~A7?x={4?D@!YIzjMieq>k!w4e|bH#Z=p8 z%i&NnHpNa`7^C*=R(}{Jv&tnm+oKJfC3=f&jI|9~d!o3`S!oYV!#?kx7EU}inq|6Y z1?#m>_U0kJzMgTz_sQNP#Mt(Z&)sBBsA&GiGp=JEX)6r`ewQi272zNkP~K ziV{AGoqp&WS(&Ybz*rShY@g_!t?X4G9cCM1I(v0F#x=ZzQs%T|*=l32QzsixC&+VC z*%o*OAV|oStn|vY#FKfVTjz5wv@AwUe-7{Ewqn0sybNy?O`&+d2>VoD@lOCZ-S;PZ z`u$_U=~mIw9)DQUP#Mm%dNfTnGv~{y^aq-T6yAas?RV(q z+QEVFzpxY_07!n48dwexhp2%%9e>l9^m)xR&jJMQ5+CUS!kR#r4_6 zkKPX8@QXcry*PT`Z($e*cAkQrz5cPWx&HO6-M}%ToqvL}`I;}KjhL#OkKFL|5!yRC zL#H9p3n#>zOYKnP-mRX*Y;n#s5;oP)?7!;R-A

gr^MA5MSa)p?t2HgC{fE|B(s;rbHXQ}E9f1= z1@Hv;mI>(9zGpTV6?Z({ch_~VG{Xtye61=CXN-BV)xvKSJuFjc%zUrVwU%lP-J;H= wGE@51F}CRc`}kWN{MS2lG^e)at};8BnN;e!91maY@iGNhj%?Zg0k5xuSm@T3j{pDw delta 2187 zcmV;62z2-S67ms{e}AP}U2oes7JcWhAh28P6C+IeP@r!~+@{%?e4s8m*e8RgXp4fi{u3LUf6NubD{Tei z2C*Vzw2-6_Whto6Q`qr$8NP8Jzce4wb4hDcaiGq+$CO}jK!$&h&xi?AW$sUJ-u5L- zc(=UA`^$^p5Uh5UA0({=Gg2@NG$%pe6oXsu#4?Vym>^`z$`CGy`7)K^%F< zI9Otb)~3kTb$@<%Rx88Hl54XnXt|+18;lOh@U?$UmfO1YV+t7@kl`n>mXN^+&qx!?;reJc0j=_aAYdBO9gR zp%yv0=YOoAvQ3-FH0*oe?wEWGU|_{G?HI-67uS+X- zV7RB^U{36GTEAhk28vrKZlEZ<7~&bVH$m5wJ%4Yts%74X3>Q-mxg_`CiH$0AA|!b~ z#|a%23yI$^&{u9?)rL@+lLaJmZOnL}Is{?s8$6+{zkj*=x4kj`QQp}L@!8g3^Ut|7n_cLca0Kv(1sVJ!sY zz0%%zVShZI5SdpvV6nsE1dA>gP*fmVEY~~D?LetJA2tho0!PaoXJT{krhw>avI{;9 zrc%#q9n?opgvL}_@QskC%t00=8M$RRntz}XmMa(RR*QA*wn84beNhky(~|ZIr(oQG zQLM*iawcDqB`nHSA5dNB47R88aAp+&8x*_2qT1VOBy&br;!#NkEUgPZYRJErj}J5A z#N$BqvBPyvbY-9#aNSw1=s=g&0gY}n?+VLPG)GkLsb#n2@!TCx(qBJmwHIxg^M4BD z|@v&66<+=oay}M{@gIkzGfKt{@o?Pv8x$q~XGgQm88IF8u&*(+bA$=Wm zIpC4FMWRt{eWh`VP8D)jTausdDm6=Vq92XjXtX?3YgHOz71`914fyTs5*j$R3P(8l zT2Ip~0*>W`!QMp~46186ua8LxynkJRAapd6mG!B3p2_}OZhbHtOZ7>ChY`*9xcMGE z)*2c*Q=IRGU=((vko&`fvfqIs!f6O9sm#TI!H*&*ApPzdB<_&tN{VVgK+bIRT8DrM zc`z8egL>w1u2J#X&_?cm6xiXj(8k6(H-XEcut&u@hlYi!>&JiZA9JT{b$=kdel4~T z92-wgjk7Y=brh0AkL5+icUH0MZDeC=-E<9xqY9ei+osqBw^ZmmXCMRQhL)^`jBv$S z>}h#AO*kF8qk%`@7J(B4T8ePm0+rr!=}wUIDprTb9X?bsK30ET*Cn3Ro@9kfZtLCA z;*#TXe>r||UnEDAX4D@&jeqQ9Cx{UQATh&)uLPk-gOEkEtFOd#OP_9jUzj5hJ@~6B zvh-I|BwS0mqd{oV`*rR377N${90xd#@X*zvJLq@0i$U_pIF)eF*;476_kKvN4{#7|I2k%aEKLOVLyyUP!6oi1|XlMUbj#(!8D8%EqR#!QM5 z%0uKjNMxND(GwLOd$UdWQ&cw0AgH5yWD*X!Y$B&Vc0 z5zQ$XPI2XB)H@H(nVK7L%^c+mUg|^9r=OhF;C}Ctk(4^p;|3D^(Ir!}KPQJ{&DfNE zI$=!OvpZ?E%4=WTTz`)?a+Vkr*%a#}-|kpB0?fzPX!+ z^!j?n4c|9+kC0-!J3fDtIYH6Fjc2N30qI(TV||SGqG!2WJ8-O%-HqyqlbRbtl7`CL zlEDRuH|%W?(Gb65l5m!>W$?~2X*jpnF8(I+) zsA{`a5E!dc$?Xf>^Od^_q~mO3Oz+-bo#Pr`LZu2?@qD#$*Qt{?&=klETe}u`1t2KM zwW`g^x5Sfql3V8sA&n}}Z2dKSNVgUL>v9Snnl11x`R2}`e=h{xYFfEt1B>r%02;ga6YiGnzBYzkJF>tBP@`S6ne;I*6)N@?(}-G2`gR3BUgYHto^TTp?Lm8BD^ zw=Kfy#rCjT(~`R1#NNiy6oi%9tXvEIMw%^%`FsNyR%pfDz22PgiRc<+KHk+$hR2TU zVy$ZfJ<~h~bnshE4@;)3oxgOtHcGES{Y(L8wJG|0jxG5e`xmfq76;j|Z>R)6T<^)i NE@$TdKLmGK8+cKEN*e$G diff --git a/dev/overview/overview/index.html b/dev/overview/overview/index.html index 4a68d386..6dd5014e 100644 --- a/dev/overview/overview/index.html +++ b/dev/overview/overview/index.html @@ -288,7 +288,7 @@ end sim = eval(gensim(Diffusion)) -fₘ = sim(periodic_mesh, generate, DiagonalHodge())

(::Main.var"#f#4"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, Main.var"#2#3", LinearAlgebra.Diagonal{Float64, Vector{Float64}}, SparseArrays.SparseMatrixCSC{Int8, Int32}, LinearAlgebra.Diagonal{Float64, Vector{Float64}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

We go ahead and set up our initial conditions for this problem. In this case we generate a Gaussian and apply it to our mesh.

using Distributions
+fₘ = sim(periodic_mesh, generate, DiagonalHodge())
(::Main.var"#f#6"{PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, PreallocationTools.FixedSizeDiffCache{Vector{Float64}, Vector{ForwardDiff.Dual{nothing, Float64, 12}}}, Main.var"#2#3", LinearAlgebra.Diagonal{Float64, Vector{Float64}}, SparseArrays.SparseMatrixCSC{Int8, Int32}, LinearAlgebra.Diagonal{Float64, Vector{Float64}}, SparseArrays.SparseMatrixCSC{Int8, Int32}}) (generic function with 1 method)

We go ahead and set up our initial conditions for this problem. In this case we generate a Gaussian and apply it to our mesh.

using Distributions
 c_dist = MvNormal([7, 5], [1.5, 1.5])
 c = [pdf(c_dist, [p[1], p[2]]) for p in periodic_mesh[:point]]
 
@@ -789,4 +789,4 @@
 record(fig, "diff_adv.gif", range(0.0, 100.0; length=150); framerate = 30) do t
   pmsh.color = sol(t).C[point_map]
 end
"diff_adv.gif"

Your first composed Decapode!

[ Info: Page built in 34 seconds.
-[ Info: This page was last built at 2024-06-13T15:13:08.205.
+[ Info: This page was last built at 2024-06-13T20:04:06.426. diff --git a/dev/poiseuille/poiseuille/index.html b/dev/poiseuille/poiseuille/index.html index 7b778413..c39cf2f0 100644 --- a/dev/poiseuille/poiseuille/index.html +++ b/dev/poiseuille/poiseuille/index.html @@ -314,4 +314,4 @@ ComponentVector{Float64}(q = [6.191992507394705e13, -8.567979424049697e13, 1.9201063923323504e13, 4.3782178252160254e12, 1.8030315315600098e11, 2.8426426848218614e8, 710.8746080616181, 6.748007174123632, 6.604264003184105, 6.922786733820603, 6.682957266911191, 6.945403323059743, 6.711137618204406, 6.656096082264093, 6.479123002819502, 6.147464654651834, 6.0393618316538, 5.652147818372922, 6.154271690686492], ρ = [5.0, 1.7757938426905157e29, -6.814063376444455e28, -1.3074140954487203e28, -5.179044153438138e26, -8.152399905268033e23, -2.0200371105361587e18, -1.2631584523233224e7, 147.2461061913548, 56.36699131400445, 74.77129872544576, 19.020919678896387, 7.77362053745385, 24.816711099030634, -7.713715579414645, 9.350784798695937, -41.20554006890849, -6.373736972474428, -107.83102822687196, 5.0]) ComponentVector{Float64}(q = [1.1872271742208814e14, -1.6427891326322578e14, 3.681533018120626e13, 8.394614772117885e12, 3.4570584996538104e11, 5.450366177513396e8, 1357.0292842418091, 6.748007178150859, 6.6042640031840945, 6.922786733820602, 6.6829572669111865, 6.945403323059738, 6.711137618204411, 6.656096082264087, 6.479123002819504, 6.147464654651828, 6.039361831653803, 5.652147818372913, 6.154271690686511], ρ = [5.0, 6.528288582778251e29, -2.5050302052818274e29, -4.806400555751653e28, -1.9039538266899406e27, -2.9970381735307163e24, -7.426191586687182e18, -4.6034136041072555e7, 147.2461048352682, 56.3669913140081, 74.7712987254481, 19.020919678900064, 7.773620537453578, 24.816711099031508, -7.713715579413784, 9.350784798697658, -41.20554006890773, -6.37373697247144, -107.83102822687206, 5.0]) ComponentVector{Float64}(q = [2.2763124383665466e14, -3.14977741197434e14, 7.058732804821097e13, 1.6095290300619385e13, 6.628339915924113e11, 1.0450178891610495e9, 2595.9077082757617, 6.748007185817237, 6.604264003184089, 6.922786733820601, 6.682957266911184, 6.945403323059735, 6.711137618204414, 6.656096082264084, 6.479123002819505, 6.147464654651825, 6.039361831653805, 5.652147818372908, 6.154271690686521], ρ = [5.0, 2.3999125809047874e30, -9.208927315306011e29, -1.7669165534657745e29, -6.999265863648581e27, -1.1017634310287478e25, -2.7299973773030162e19, -1.6845665050515872e8, 147.24610225132864, 56.366991314009994, 74.77129872544933, 19.020919678901983, 7.773620537453436, 24.816711099031963, -7.713715579413335, 9.350784798698557, -41.20554006890733, -6.3737369724698825, -107.83102822687212, 5.0])

Notice that the solution contains both a vector of flows and a vector of pressures.

[ Info: Page built in 16 seconds.
-[ Info: This page was last built at 2024-06-13T15:13:24.134.
+[ Info: This page was last built at 2024-06-13T20:04:22.143. diff --git a/dev/search_index.js b/dev/search_index.js index 018aab06..9283e7b5 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"bc/bc_debug/#Simulation-Setup","page":"Misc Features","title":"Simulation Setup","text":"","category":"section"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"This tutorial showcases some of the other features included in the Decapodes.jl package. Currently, these features are the treatment of boundary conditions and the simulation debugger interface. To begin, we set up the same advection-diffusion problem presented in the Overview section. As before, we define the Diffusion, Advection, and Superposition components, and now include a Boundary Condition (BC) component. By convention, BCs are encoded in Decapodes by using a ∂ symbol. Below we show the graphical rendering of this boundary condition diagram, which we will use to impose a Dirichlet condition on the time derivative of concentration at the mesh boundary.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using Catlab\nusing DiagrammaticEquations\nusing Decapodes\n\nDiffusion = @decapode begin\n C::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\nend\n\nAdvection = @decapode begin\n C::Form0\n ϕ::Form1\n V::Constant\n\n ϕ == ∧₀₁(C,V)\nend\n\nSuperposition = @decapode begin\n (C, C_up)::Form0\n (ϕ, ϕ₁, ϕ₂)::Form1\n\n ϕ == ϕ₁ + ϕ₂\n C_up == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\nend\n\nBoundaryConditions = @decapode begin\n (C, C_up)::Form0\n\n # Temporal boundary\n ∂ₜ(C) == Ċ\n\n # Spatial boundary\n Ċ == ∂C(C_up)\nend\n\nto_graphviz(BoundaryConditions)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"As before, we compose these physics components over our wiring diagram.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"compose_diff_adv = @relation (C, V) begin\n diffusion(C, ϕ₁)\n advection(C, ϕ₂, V)\n bc(C, C_up)\n superposition(ϕ₁, ϕ₂, ϕ, C_up, C)\nend\n\ndraw_composition(compose_diff_adv)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"DiffusionAdvection_cospan = oapply(compose_diff_adv,\n [Open(Diffusion, [:C, :ϕ]),\n Open(Advection, [:C, :ϕ, :V]),\n Open(BoundaryConditions, [:C, :C_up]),\n Open(Superposition, [:ϕ₁, :ϕ₂, :ϕ, :C_up, :C])])\nDiffusionAdvection = apex(DiffusionAdvection_cospan)\n\nto_graphviz(DiffusionAdvection)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"When this is scheduled, Decapodes will apply any boundary conditions immediately after the impacted value is computed. This implementation choice ensures that this boundary condition holds true for any variables dependent on this variable, though also means that the boundary conditions on a variable have no immediate impact on the variables this variable is dependent on.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"In the visualization below, we see that the final operation executed on the data is the boundary condition we are enforcing on the change in concentration.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"to_graphviz(DiffusionAdvection)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"Next we import the mesh we will use. In this case, we are wanting to impose boundary conditions and so we will use the plot_mesh from the previous example instead of the mesh with periodic boundary conditions. Because the mesh is only a primal mesh, we also generate and subdivide the dual mesh.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using CombinatorialSpaces\nusing CairoMakie\n\nplot_mesh = loadmesh(Rectangle_30x10())\n\n# Generate the dual mesh\nplot_mesh_dual = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3{Float64}}(plot_mesh)\n\n# Calculate distances and subdivisions for the dual mesh\nsubdivide_duals!(plot_mesh_dual, Circumcenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nwireframe!(ax, plot_mesh)\nfig","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"Finally, we define our operators, generate the simulation function, and compute the simulation. Note that when we define the boundary condition operator, we hardcode the boundary indices and values into the operator itself. We also move the initial concentration to the left, so that we are able to see a constant concentration on the left boundary which will act as a source in the flow. You can find the file for boundary conditions here. The modified initial condition is shown below:","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using LinearAlgebra\nusing ComponentArrays\nusing MLStyle\ninclude(\"../boundary_helpers.jl\")\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :k => x -> 0.05*x\n :∂C => x -> begin\n boundary = boundary_inds(Val{0}, sd)\n x[boundary] .= 0\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend\n\nusing Distributions\nc_dist = MvNormal([1, 5], [1.5, 1.5])\nc = [pdf(c_dist, [p[1], p[2]]) for p in plot_mesh_dual[:point]]\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nmesh!(ax, plot_mesh; color=c[1:nv(plot_mesh)])\nfig","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"And the simulation result is then computed and visualized below.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using OrdinaryDiffEq\n\nsim = eval(gensim(DiffusionAdvection))\nfₘ = sim(plot_mesh_dual, generate)\n\nvelocity(p) = [-0.5, 0.0, 0.0]\nv = ♭(plot_mesh_dual, DualVectorField(velocity.(plot_mesh_dual[triangle_center(plot_mesh_dual),:dual_point]))).data\n\nu₀ = ComponentArray(C=c)\nparams = (V = v,)\n\nprob = ODEProblem(fₘ, u₀, (0.0, 100.0), params)\nsol = solve(prob, Tsit5());\n\n# Plot the result\ntimes = range(0.0, 100.0, length=150)\ncolors = [sol(t).C for t in times]\nextrema\n# Initial frame\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\npmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))\nColorbar(fig[1,2], pmsh)\nframerate = 30\n\n# Animation\nrecord(fig, \"diff_adv_right.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n pmsh.color = sol(t).C\nend","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"(Image: Diffusion-Advection result and your first BC Decapode!)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"navier_stokes/ns/#Navier-Stokes-Vorticity-Model","page":"Vortices","title":"Navier Stokes Vorticity Model","text":"","category":"section"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"This is a discretization of the incompressible Navier Stokes equations using the Discrete Exterior Calculus.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"The formulations are based on those given by Mohamed, Hirani, Samtaney (in turn from Marsden, Ratiu, Abraham).","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"However, different choices in discretization are chosen for purposes of brevity, to demonstrate novel discretizations of certain operators, and to demonstrate the automated Decapodes workflow.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"The different formulations are given in the following decapode expressions. The full code that generated these results is available in a julia script.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"eq11_vorticity = @decapode begin\n d𝐮::DualForm2\n 𝐮::DualForm1\n μ::Constant\n\n 𝐮 == d₁⁻¹(d𝐮)\n\n ∂ₜ(d𝐮) == μ * ∘(⋆, d, ⋆, d)(d𝐮) + (-1) * ∘(♭♯, ⋆₁, d̃₁)(∧ᵈᵖ₁₀(𝐮, ⋆(d𝐮)))\nend\n\neq11_inviscid_vorticity = @decapode begin\n d𝐮::DualForm2\n 𝐮::DualForm1\n\n 𝐮 == d₁⁻¹(d𝐮)\n\n ∂ₜ(d𝐮) == (-1) * ∘(♭♯, ⋆₁, d̃₁)(∧ᵈᵖ₁₀(𝐮, ⋆(d𝐮)))\nend\n\neq11_inviscid_poisson = @decapode begin\n d𝐮::DualForm2\n 𝐮::DualForm1\n ψ::Form0\n\n ψ == Δ⁻¹(⋆(d𝐮))\n 𝐮 == ⋆(d(ψ))\n\n ∂ₜ(d𝐮) == (-1) * ∘(♭♯, ⋆₁, d̃₁)(∧ᵈᵖ₁₀(𝐮, ⋆(d𝐮)))\nend\n\neq17_stream = @decapode begin\n ψ::Form0\n u::DualForm1\n v::Form1\n μ::Constant\n\n u == ⋆(d(ψ))\n v == ⋆(u)\n\n ∂ₜ(ψ) == dsdinv(\n μ * ∘(d, ⋆, d, ⋆, d, ⋆, d)(ψ) -\n ∘(⋆₁, d̃₁)(v ∧ ∘(d,⋆,d,⋆)(ψ)))\nend\n\neq17_inviscid_stream = @decapode begin\n ψ::Form0\n u::DualForm1\n v::Form1\n\n u == ⋆(d(ψ))\n v == ⋆(u)\n\n ∂ₜ(ψ) == -1 * dsdinv(∘(⋆₁, d̃₁)(v ∧ ∘(d,⋆,d,⋆)(ψ)))\nend","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"Our initial conditions of interest are either Taylor or Point vortices","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"function taylor_vortex(pnt::Point3D, cntr::Point3D, p::TaylorVortexParams)\n gcd = great_circle_dist(pnt,cntr)\n (p.G/p.a) * (2 - (gcd/p.a)^2) * exp(0.5 * (1 - (gcd/p.a)^2))\nend\n\nfunction point_vortex(pnt::Point3D, cntr::Point3D, p::PointVortexParams)\n gcd = great_circle_dist(pnt,cntr)\n p.τ / (cosh(3gcd/p.a)^2)\nend","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"Based on the configuration, you can see different results that match the expected solutions from the literature.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"Here is one set of results from using the inviscid Poisson formulation:","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"(Image: Vorticity)","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"These vortices should be stable so we should see the same periodic function for both lines here. The difference between the lines is the accumulated error.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"(Image: Azimuth Profile)","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"equations/equations/#Simple-Equations","page":"Equations","title":"Simple Equations","text":"","category":"section"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\",\"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"This tutorial shows how to use Decapodes to represent simple equations. These aren't using any of the Discrete Exterior Calculus or CombinatorialSpaces features of Decapodes. They just are a reference for how to build equations with the @decapodes macro and see how they are stored as ACSets.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"using Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"The harmonic oscillator can be written in Decapodes in at least three different ways.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -k(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"The default representation is a tabular output as an ACSet. The tables are Var for storing variables (X) and their types (Form0). TVar for identifying a subset of variables that are the tangent variables of the dynamics (Ẋ). The unary operators are stored in Op1 and binary operators stored in Op2. If a table is empty, it doesn't get printed.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"Even though a diagrammatic equation is like a graph, there are no edge tables, because the arity (number of inputs) and coarity (number of outputs) is baked into the operator definitions.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"You can also see the output as a directed graph. The input arrows point to the state variables of the system and the output variables point from the tangent variables. You can see that I have done the differential degree reduction from x'' = -kx by introducing a velocity term v. Decapodes has some support for derivatives in the visualization layer, so it knows that dX/dt should be called Ẋ and that dẊ/dt should be called Ẋ̇.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"In the previous example, we viewed negation and transformation by k as operators. Notice that k appears as an edge in the graph and not as a vertex. You can also use a 2 argument function like multiplication (*). With a constant value for k::Constant. In this case you will see k enter the diagram as a vertex and multiplication with * as a binary operator.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n k::Constant\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -k*(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"This gives you a different graphical representation as well. Now we have the cartesian product objects which represent a tupling of two values.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"You can also represent negation as a multiplication by a literal -1.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n k::Constant\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -1*k*(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"Notice that the type bubble for the literal one is ΩL. This means that it is a literal. The literal is also used as the variable name.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"We can allow the material properties to vary over time by changing Constant to Parameter. This is how we tell the simulator that it needs to call k(t) at each time step to get the updated value for k or if it can just reuse that constant k from the initial time step.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n k::Parameter\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -1*k*(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"Often you will have a linear material where you are scaling by a constant, and a nonlinear version of that material where that scaling is replaced by a generic nonlinear function. This is why we allow Decapodes to represent both of these types of equations.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"canon/#Canon","page":"Canonical Models","title":"Canon","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"include(joinpath(Base.@__DIR__, \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"canon/#Physics","page":"Canonical Models","title":"Physics","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Physics ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Physics.:heat_transfer","page":"Canonical Models","title":"Decapodes.Canon.Physics.:heat_transfer","text":"Heat Transfer\n\nSource\n\nModel\n\n(HT, Tₛ)::Form0\n \n(D, cosϕᵖ, cosϕᵈ)::Constant\n \nHT == (D ./ cosϕᵖ) .* (⋆)(d(cosϕᵈ .* (⋆)(d(Tₛ))))\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.:outgoing_longwave_radiation","page":"Canonical Models","title":"Decapodes.Canon.Physics.:outgoing_longwave_radiation","text":"Outgoing Longwave Radiation\n\nSource\n\nModel\n\n(Tₛ, OLR)::Form0\n \n(A, B)::Constant\n \nOLR == A .+ B .* Tₛ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.absorbed_shortwave_radiation","page":"Canonical Models","title":"Decapodes.Canon.Physics.absorbed_shortwave_radiation","text":"Absorbed Shortwave Radiation\n\nSource\n\nThe proportion of light reflected by a surface is the albedo. The absorbed shortwave radiation is the complement of this quantity.\n\nModel\n\n(Q, ASR)::Form0\n \nα::Constant\n \nASR == (1 .- α) .* Q\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.advection","page":"Canonical Models","title":"Decapodes.Canon.Physics.advection","text":"Advection\n\nSource\n\nAdvection refers to the transport of a bulk along a vector field.\n\nModel\n\nC::Form0\n \n(ϕ, V)::Form1\n \nϕ == C ∧₀₁ V\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.ficks_law","page":"Canonical Models","title":"Decapodes.Canon.Physics.ficks_law","text":"Ficks Law\n\nSource\n\nEquation for diffusion first stated by Adolf Fick. The diffusion flux is proportional to the concentration gradient.\n\nModel\n\nC::Form0\n \nϕ::Form1\n \nϕ == k(d₀(C))\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.iceblockingwater","page":"Canonical Models","title":"Decapodes.Canon.Physics.iceblockingwater","text":"IceBlockingWater\n\nSource\n\nModel\n\nh::Form0\n \n(𝐮, w)::DualForm1\n \nw == (1 - σ(h)) ∧ᵖᵈ₀₁ 𝐮\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.jko_scheme","page":"Canonical Models","title":"Decapodes.Canon.Physics.jko_scheme","text":"Jordan-Kinderlehrer-Otto\n\nSource\n\nJordan, R., Kinderlehrer, D., & Otto, F. (1998). The Variational Formulation of the Fokker–Planck Equation. In SIAM Journal on Mathematical Analysis (Vol. 29, Issue 1, pp. 1–17). Society for Industrial & Applied Mathematics (SIAM). https://doi.org/10.1137/s0036141096303359\n\nModel\n\n(ρ, Ψ)::Form0\n \nβ⁻¹::Constant\n \n∂ₜ(ρ) == (∘(⋆, d, ⋆))(d(Ψ) ∧ ρ) + β⁻¹ * Δ(ρ)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.lie","page":"Canonical Models","title":"Decapodes.Canon.Physics.lie","text":"Lie\n\nSource\n\nModel\n\nC::Form0\n \nV::Form1\n \ndX::Form1\n \nV == ((⋆) ∘ (⋆))(C ∧ dX)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.mohamed_flow","page":"Canonical Models","title":"Decapodes.Canon.Physics.mohamed_flow","text":"Mohamed Eq. 10, N2\n\nSource\n\nModel\n\n(𝐮, w)::DualForm1\n \n(P, 𝑝ᵈ)::DualForm0\n \nμ::Constant\n \n𝑝ᵈ == P + 0.5 * ι₁₁(w, w)\n \n∂ₜ(𝐮) == μ * (∘(d, ⋆, d, ⋆))(w) + -1 * (⋆₁⁻¹)(w ∧ᵈᵖ₁₀ (⋆)(d(w))) + d(𝑝ᵈ)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.momentum","page":"Canonical Models","title":"Decapodes.Canon.Physics.momentum","text":"Momentum\n\nSource\n\nModel\n\n(f, b)::Form0\n \n(v, V, g, Fᵥ, uˢ, v_up)::Form1\n \nτ::Form2\n \nU::Parameter\n \nuˢ̇ == ∂ₜ(uˢ)\n \nv_up == (((((((-1 * L(v, v) - L(V, v)) - L(v, V)) - f ∧ v) - (∘(⋆, d, ⋆))(uˢ) ∧ v) - d(p)) + b ∧ g) - (∘(⋆, d, ⋆))(τ)) + uˢ̇ + Fᵥ\n \nuˢ̇ == force(U)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.navier_stokes","page":"Canonical Models","title":"Decapodes.Canon.Physics.navier_stokes","text":"Navier-Stokes\n\nSource\n\nPartial differential equations which describe the motion of viscous fluid surfaces.\n\nModel\n\n(V, V̇, G)::Form1{X}\n \n(ρ, ṗ, p)::Form0{X}\n \nV̇ == neg₁(L₁′(V, V)) + div₁(kᵥ(Δ₁(V) + third(d₀(δ₁(V)))), avg₀₁(ρ)) + d₀(half(i₁′(V, V))) + neg₁(div₁(d₀(p), avg₀₁(ρ))) + G\n \n∂ₜ(V) == V̇\n \nṗ == neg₀((⋆₀⁻¹)(L₀(V, (⋆₀)(p))))\n \n∂ₜ(p) == ṗ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.oscillator","page":"Canonical Models","title":"Decapodes.Canon.Physics.oscillator","text":"Oscillator\n\nSource\n\nEquation governing the motion of an object whose acceleration is negatively-proportional to its position.\n\nModel\n\nX::Form0\n \nV::Form0\n \nk::Constant\n \n∂ₜ(X) == V\n \n∂ₜ(V) == -k * X\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.poiseuille","page":"Canonical Models","title":"Decapodes.Canon.Physics.poiseuille","text":"Poiseuille\n\nSource\n\nA relation between the pressure drop in an incompressible and Newtownian fluid in laminar flow flowing through a long cylindrical pipe.\n\nModel\n\nP::Form0\n \nq::Form1\n \n(R, μ̃)::Constant\n \nΔq == Δ(q)\n \n∇P == d(P)\n \n∂ₜ(q) == q̇\n \nq̇ == μ̃ * ∂q(Δq) + ∇P + R * q\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.poiseuille_density","page":"Canonical Models","title":"Decapodes.Canon.Physics.poiseuille_density","text":"Poiseuille Density\n\nSource\n\nModel\n\nq::Form1\n \n(P, ρ)::Form0\n \n(k, R, μ̃)::Constant\n \n∂ₜ(q) == q̇\n \n∇P == d(P)\n \nq̇ == (μ̃ * ∂q(Δ(q)) - ∇P) + R * q\n \nP == k * ρ\n \n∂ₜ(ρ) == ρ̇\n \nρ_up == (∘(⋆, d, ⋆))(-1 * (ρ ∧₀₁ q))\n \nρ̇ == ∂ρ(ρ_up)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.schroedinger","page":"Canonical Models","title":"Decapodes.Canon.Physics.schroedinger","text":"Schroedinger\n\nSource\n\nThe evolution of the wave function over time.\n\nModel\n\n(i, h, m)::Constant\n \nV::Parameter\n \nΨ::Form0\n \n∂ₜ(Ψ) == (((-1 * h ^ 2) / (2m)) * Δ(Ψ) + V * Ψ) / (i * h)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.superposition","page":"Canonical Models","title":"Decapodes.Canon.Physics.superposition","text":"Superposition\n\nSource\n\nModel\n\n(C, Ċ)::Form0\n \n(ϕ, ϕ₁, ϕ₂)::Form1\n \nϕ == ϕ₁ + ϕ₂\n \nĊ == (⋆₀⁻¹)(dual_d₁((⋆₁)(ϕ)))\n \n∂ₜ(C) == Ċ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Chemistry","page":"Canonical Models","title":"Chemistry","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Chemistry ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Chemistry.GrayScott","page":"Canonical Models","title":"Decapodes.Canon.Chemistry.GrayScott","text":"Gray-Scott\n\nSource\n\nA model of reaction-diffusion\n\nModel\n\n(U, V)::Form0\n \nUV2::Form0\n \n(U̇, V̇)::Form0\n \n(f, k, rᵤ, rᵥ)::Constant\n \nUV2 == U .* (V .* V)\n \nU̇ == (rᵤ * Δ(U) - UV2) + f * (1 .- U)\n \nV̇ == (rᵥ * Δ(V) + UV2) - (f + k) .* V\n \n∂ₜ(U) == U̇\n \n∂ₜ(V) == V̇\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Chemistry.brusselator","page":"Canonical Models","title":"Decapodes.Canon.Chemistry.brusselator","text":"Brusselator\n\nSource\n\nA model of reaction-diffusion for an oscillatory chemical system.\n\nModel\n\n(U, V)::Form0\n \nU2V::Form0\n \n(U̇, V̇)::Form0\n \nα::Constant\n \nF::Parameter\n \nU2V == (U .* U) .* V\n \nU̇ == ((1 + U2V) - 4.4U) + α * Δ(U) + F\n \nV̇ == (3.4U - U2V) + α * Δ(V)\n \n∂ₜ(U) == U̇\n \n∂ₜ(V) == V̇\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Biology","page":"Canonical Models","title":"Biology","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Biology ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Biology.kealy","page":"Canonical Models","title":"Decapodes.Canon.Biology.kealy","text":"Kealy\n\nSource\n\nModel\n\n(n, w)::DualForm0\n \ndX::Form1\n \n(a, ν)::Constant\n \n∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * Δ(w)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.klausmeier_2a","page":"Canonical Models","title":"Decapodes.Canon.Biology.klausmeier_2a","text":"Klausmeier (Eq. 2a)\n\nSource\n\nKlausmeier, CA. “Regular and irregular patterns in semiarid vegetation.” Science (New York, N.Y.) vol. 284,5421 (1999): 1826-8. doi:10.1126/science.284.5421.1826\n\nModel\n\n(n, w)::DualForm0\n \ndX::Form1\n \n(a, ν)::Constant\n \n∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * ℒ(dX, w)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.klausmeier_2b","page":"Canonical Models","title":"Decapodes.Canon.Biology.klausmeier_2b","text":"Klausmeier (Eq. 2b)\n\nSource\n\nibid.\n\nModel\n\n(n, w)::DualForm0\n \nm::Constant\n \n∂ₜ(n) == (w * n ^ 2 - m * n) + Δ(n)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.lejeune","page":"Canonical Models","title":"Decapodes.Canon.Biology.lejeune","text":"Lejeune\n\nSource\n\nLejeune, O., & Tlidi, M. (1999). A Model for the Explanation of Vegetation Stripes (Tiger Bush). Journal of Vegetation Science, 10(2), 201–208. https://doi.org/10.2307/3237141\n\nModel\n\nρ::Form0\n \n(μ, Λ, L)::Constant\n \n∂ₜ(ρ) == (ρ * (((1 - μ) + (Λ - 1) * ρ) - ρ * ρ) + 0.5 * (L * L - ρ) * Δ(ρ)) - 0.125 * ρ * Δ(ρ) * Δ(ρ)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.turing_continuous_ring","page":"Canonical Models","title":"Decapodes.Canon.Biology.turing_continuous_ring","text":"Turing Continuous Ring\n\nSource\n\nModel\n\n(X, Y)::Form0\n \n(μ, ν, a, b, c, d)::Constant\n \n∂ₜ(X) == a * X + b * Y + μ * Δ(X)\n \n∂ₜ(Y) == c * X + d * Y + ν * Δ(X)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Environment","page":"Canonical Models","title":"Environment","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Environment ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Environment.boundary_conditions","page":"Canonical Models","title":"Decapodes.Canon.Environment.boundary_conditions","text":"Boundary Conditions\n\nSource\n\nModel\n\n(S, T)::Form0\n \n(Ṡ, T_up)::Form0\n \nv::Form1\n \nv_up::Form1\n \nṪ == ∂ₜ(T)\n \nṠ == ∂ₜ(S)\n \nv̇ == ∂ₜ(v)\n \nṪ == ∂_spatial(T_up)\n \nv̇ == ∂_noslip(v_up)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.energy_balance","page":"Canonical Models","title":"Decapodes.Canon.Environment.energy_balance","text":"Energy balance\n\nSource\n\nenergy balance equation from Budyko Sellers\n\nModel\n\n(Tₛ, ASR, OLR, HT)::Form0\n \nC::Constant\n \nTₛ̇ == ∂ₜ(Tₛ)\n \nTₛ̇ == ((ASR - OLR) + HT) ./ C\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.equation_of_state","page":"Canonical Models","title":"Decapodes.Canon.Environment.equation_of_state","text":"Equation of State\n\nSource\n\nModel\n\n(b, T, S)::Form0\n \n(g, α, β)::Constant\n \nb == g * (α * T - β * S)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.glen","page":"Canonical Models","title":"Decapodes.Canon.Environment.glen","text":"Glens Law\n\nSource\n\nNye, J. F. (1957). The Distribution of Stress and Velocity in Glaciers and Ice-Sheets. Proceedings of the Royal Society of London. Series A, Mathematical and Physical Sciences, 239(1216), 113–133. http://www.jstor.org/stable/100184\n\nModel\n\nΓ::Form1\n \n(A, ρ, g, n)::Constant\n \nΓ == (2 / (n + 2)) * A * (ρ * g) ^ n\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.halfar_eq2","page":"Canonical Models","title":"Decapodes.Canon.Environment.halfar_eq2","text":"Halfar (Eq. 2)\n\nSource\n\nHalfar, P. (1981), On the dynamics of the ice sheets, J. Geophys. Res., 86(C11), 11065–11072, doi:10.1029/JC086iC11p11065\n\nModel\n\nh::Form0\n \nΓ::Form1\n \nn::Constant\n \n∂ₜ(h) == (∘(⋆, d, ⋆))(((Γ * d(h)) ∧ mag(♯(d(h))) ^ (n - 1)) ∧ h ^ (n + 2))\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.insolation","page":"Canonical Models","title":"Decapodes.Canon.Environment.insolation","text":"Insolation\n\nSource\n\nModel\n\nQ::Form0\n \ncosϕᵖ::Constant\n \nQ == 450cosϕᵖ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.tracer","page":"Canonical Models","title":"Decapodes.Canon.Environment.tracer","text":"Tracer\n\nSource\n\nModel\n\n(c, C, F, c_up)::Form0\n \n(v, V, q)::Form1\n \nc_up == (((-1 * (⋆)(L(v, (⋆)(c))) - (⋆)(L(V, (⋆)(c)))) - (⋆)(L(v, (⋆)(C)))) - (∘(⋆, d, ⋆))(q)) + F\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.warming","page":"Canonical Models","title":"Decapodes.Canon.Environment.warming","text":"Warming\n\nSource\n\nModel\n\nTₛ::Form0\n \nA::Form1\n \nA == avg₀₁(5.8282 * 10 ^ (-0.236Tₛ) * 1.65e7)\n\n\n\n\n\n","category":"constant"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"api/#Library-Reference","page":"Library Reference","title":"Library Reference","text":"","category":"section"},{"location":"api/","page":"Library Reference","title":"Library Reference","text":"include(joinpath(Base.@__DIR__, \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"api/#Decapodes","page":"Library Reference","title":"Decapodes","text":"","category":"section"},{"location":"api/","page":"Library Reference","title":"Library Reference","text":"Modules = [ Decapodes ]\nPrivate = false","category":"page"},{"location":"api/#Decapodes.gensim-Tuple{DiagrammaticEquations.decapodeacset.AbstractNamedDecapode}","page":"Library Reference","title":"Decapodes.gensim","text":"function gensim(d::AbstractNamedDecapode; dimension::Int=2)\n\nGenerate a simulation function from the given Decapode. The returned function can then be combined with a mesh and a function describing function mappings to return a simulator to be passed to solve.\n\n\n\n\n\n","category":"method"},{"location":"api/","page":"Library Reference","title":"Library Reference","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"cism/cism/#Replicating-the-Community-Ice-Sheet-Model-v2.1-Halfar-Dome-Benchmark-with-Decapodes","page":"CISM v2.1","title":"Replicating the Community Ice Sheet Model v2.1 Halfar Dome Benchmark with Decapodes","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"The Decapodes framework takes high-level representations of physics equations and automatically generates solvers.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We do so by translating equations from vector calculus notation to the \"discrete exterior calculus\" (DEC). This process is roughly about recognizing whether physical quantities represent scalar or vector quantities, and recognizing whether differential operators represent gradient, divergence, and so on.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"In this benchmark, we will implement the \"small slope approximation\" of glacial dynamics used by P. Halfar in his 1981 work \"On the dynamics of the ice sheets\" by taking his original formulation, translating it into the DEC, then providing a mesh and initial conditions.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"The initial conditions used here are exactly those considered by W. H. Lipscomb et al. in \"Description And Evaluation of the Community Ice Sheet Model (CISM) v2.1\" (2019).","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing BenchmarkTools\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point2, Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nusing Statistics\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64}\nnothing # hide","category":"page"},{"location":"cism/cism/#Specifying-and-Composing-Physics","page":"CISM v2.1","title":"Specifying and Composing Physics","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Equation 2\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We will translate Halfar's equation into the DEC below. Although the equation given by Halfar is dense, this notation does not allow you to see which operators represent divergence, which components represent diffusivity constants, and so on. In the DEC, there is a small pool of operators, ⋆, d, ∧, ♯, and ♭, which combine according to set rules to encode all of these notions.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"In the DEC, gradients are generalized by the exterior derivative \"d\". Given scalar-data, d gives the slope along edges in our mesh. Similarly, the operator (⋆, d, ⋆) generalizes divergence.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"In Halfar's equation there is a term corresponding to the magnitude of the slope, but it is not clear where this quantity is to be defined. Is it a scalar-like quantity, or a vector-like quantity? In the DEC translation, we take the gradient of h, d(h), and use the \"sharp\" operator to define it on points in the domain, where we then take its magnitude. The \"wedge product\", ∧, takes care of multiplying a scalar-like quantity by a vector-like quantity.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Halfar's equation looks a little disjoint. It seems that the front most terms are responsible for computing some parameter, while the remaining terms on the right encode something about the dynamics. This is because Halfar's equation is actually describing two equations in one. The front-most term defines a quantity - depending on the strain of the ice - that controls the rate at which ice diffuses. Since this computation is rather separate from the rest of the computations involving our differential operators, we will call it \"Gamma\" here, and define it in a later component Decapode.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Equation 2 from Halfar, P. ON THE DYNAMICS OF THE ICE SHEETS. (1981),\n# translated into the exterior calculus.\nhalfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nto_graphviz(halfar_eq2)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Glen's Law\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Here, we recognize that Gamma is in fact what glaciologists call \"Glen's Flow Law.\" It states that the strain rate of a sheet of ice can be related to applied stress via a power law. Below, we encode the formulation as it is usually given in the literature, depending explicitly on the gravitational constant, g.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Equation 1 from Glen, J. W. THE FLOW LAW OF ICE: A discussion of the\n# assumptions made in glacier theory, their experimental foundations and\n# consequences. (1958)\nglens_law = @decapode begin\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nto_graphviz(glens_law)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We now need some way to compose these physics equations together. Since this physics is rather small, and there are no naming conflicts of physical quantities, this composition is also rather simple.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"#####################\n# Compose the model #\n#####################\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\ndraw_composition(ice_dynamics_composition_diagram)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plug in our Decapodes to the composition pattern.\nice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\n\nice_dynamics = apex(ice_dynamics_cospan)\nto_graphviz(ice_dynamics, verbose=false)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We have a representation of our composed physics. Now, we need to specify that these dynamics occur in 2 dimensions.","category":"page"},{"location":"cism/cism/#Providing-a-Semantics","page":"CISM v2.1","title":"Providing a Semantics","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Interpret this multiphysics diagram in the 2D exterior calculus.\n\nice_dynamics2D = expand_operators(ice_dynamics)\ninfer_types!(ice_dynamics2D)\nresolve_overloads!(ice_dynamics2D)\nto_graphviz(ice_dynamics2D, verbose=false)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We are done encoding our dynamics. Now, we need to provide a mesh, initial data to use for our quantities, and what functions to use for our differential operators.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Our mesh library, CombinatorialSpaces, can interpret arbitrary .OBJ files to run our dynamics on. Here, we use a script that generates a triangulated grid of the resolution used in the CISM benchmark. Note though that the \"resolution\" of a triangulated and non-triangulated grid is difficult to directly compare.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"s = triangulated_grid(60_000,100_000,2_000,2_000,Point3D)\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nx̄ = mean(p -> p[1], point(sd))\nȳ = mean(p -> p[2], point(sd))\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\nwf = wireframe!(ax, sd; linewidth=0.5)\nsave(\"ice_mesh.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Wireframe of the Domain\")","category":"page"},{"location":"cism/cism/#Defining-input-data","page":"CISM v2.1","title":"Defining input data","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We replicate the initial conditions and parameter values used in the CISM benchmark.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# These are the initial conditions to the Halfar Dome test case that the\n# Community Ice Sheet Model uses.\nR₀ = 60_000 * sqrt(0.125)\nH = 2_000 * sqrt(0.125)\n\nn = 3\ng = 9.8101\nρ = 910\nalpha = 1/9\nbeta = 1/18\nflwa = 1e-16\nA = fill(1e-16, ne(sd))\n\nGamma = 2.0/(n+2) * flwa * (ρ * g)^n\nt0 = (beta/Gamma) * (7.0/4.0)^3 * (R₀^4 / H^7)\n\n# This is the analytic solution for comparison.\n# It is ported over from the CISM code for comparison's sake,\n# and we will use it to set initial conditions.\nfunction height_at_p(x,y,t)\n tr = (t + t0) / t0\n r = sqrt((x - x̄)^2 + (y - ȳ)^2)\n r = r/R₀\n inside = max(0.0, 1.0 - (r / tr^beta)^((n+1.0) / n))\n H * inside^(n / (2*n + 1)) / tr^alpha\nend\n\n# Set the initial conditions for ice sheet height:\n# Ice height is a primal 0-form. i.e. valued at vertices.\nh₀ = map(x -> height_at_p(x[1], x[2], 0), point(s))\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\nmsh = mesh!(ax, s, color=h₀, colormap=:jet)\nsave(\"ice_initial_conditions.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Initial Conditions\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Store these values to be passed to the solver.\nu₀ = ComponentArray(dynamics_h = h₀)\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We provide here the mapping from symbols to differential operators. As more of the differential operators of the DEC are implemented, they are upstreamed to the Decapodes and CombinatorialSpaces libraries. Of course, users can also provide their own implementations of these operators and others as they see fit.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"#############################################\n# Define how symbols map to Julia functions #\n#############################################\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n # We pre-allocate matrices that encode differential operators.\n op = @match my_symbol begin\n :mag => x -> norm.(x)\n :♯ => begin\n sharp_mat = ♯_mat(sd, AltPPSharp())\n x -> sharp_mat * x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"The gensim function takes our high-level representation of the physics equations and produces compiled simulation code. It performs optimizations such as allocating memory for intermediate variables, and so on.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"#######################\n# Generate simulation #\n#######################\n\nsim = eval(gensim(ice_dynamics2D))\nfₘ = sim(sd, generate)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Julia is a \"Just-In-Time\" compiled language. That means that functions are compiled the first time they are called, and later calls to those functions skip this step. To get a feel for just how fast this simulation is, we will run the dynamics twice, once for a very short timespan to trigger pre-compilation, and then again for the actual dynamics.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Pre-compile simulation\n\n# Julia will pre-compile the generated simulation the first time it is run.\n@info(\"Precompiling Solver\")\n# We run for a short timespan to pre-compile.\nprob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)\nsoln = solve(prob, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Run simulation\ntₑ = 200\n\n# This next run should be fast.\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5(), saveat=0.1)\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We can benchmark the compiled simulation with @benchmarkable. This macro runs many samples of the simulation function so we get an accurate estimate of the simulation time. The simulation time is quite fast compared to the CISM benchmarks. These results are run automatically via GitHub Actions as part of our docs build, which is not optimized for numerical simulations.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Time the simulation\n\nb = @benchmarkable solve(prob, Tsit5(), saveat=0.1)\nc = run(b)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Here we save the solution information to a file.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We recall that these dynamics are of the \"shallow slope\" and \"shallow ice\" approximations. So, at the edge of our parabolic dome of ice, we expect increased error as the slope increases. On the interior of the dome, we expect the dynamics to match more closely that given by the analytic model. We will see that the CISM results likewise accumulate error in the same neighborhood.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Small Ice Approximation Quote\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plot the final conditions\nfunction plot_final_conditions()\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1],\n title=\"Modeled thickness (m) at time 200.0\",\n aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=soln(200.0).dynamics_h, colormap=:jet)\n Colorbar(fig[1,2], msh)\n fig\nend\nfig = plot_final_conditions()\nsave(\"ice_numeric_solution.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numerical Solution\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plot the final conditions according to the analytic solution.\nfunction plot_analytic()\n hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1],\n title=\"Analytic thickness (m) at time 200.0\",\n aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=hₐ, colormap=:jet)\n Colorbar(fig[1,2], msh)\n fig\nend\nfig = plot_analytic()\nsave(\"ice_analytic_solution.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Analytic Solution)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plot the error.\nfunction plot_error()\n hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\n h_diff = soln(tₑ).dynamics_h - hₐ\n extrema(h_diff)\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1],\n title=\"Modeled thickness - Analytic thickness at time 200.0\",\n aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=h_diff, colormap=:jet)\n Colorbar(fig[1,2], msh)\n fig\nend\nfig = plot_error()\nsave(\"ice_error.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numeric Solution - Analytic Solution\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We compute below that the maximum absolute error is approximately 89 meters. We observe that this error occurs exactly on the edge of the dome, which we expect given that this is where the \"shallow slope approximation\" breaks down, and the updates to our physical quantities should become more unstable. This pattern likewise occurs in the CISM benchmarks.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute max absolute error:\nhₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\nh_diff = soln(tₑ).dynamics_h - hₐ\nmaximum(abs.(h_diff))","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We compute root-mean-square (RMS) error as well, both over the entire domain, and excluding where the ice distribution is 0 in the analytic solution. This is done since considering the entire domain decreases the RMS while not telling you much about the area of interest. Note that the official CISM benchmark reports 6.43 and 9.06 RMS for their two solver implementations.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMS not considering the \"outside\".\nhₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\nnonzeros = findall(!=(0), hₐ)\nh_diff = soln(tₑ).dynamics_h - hₐ\nrmse = sqrt(sum(map(x -> x*x, h_diff[nonzeros])) / length(nonzeros))","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMS of the entire domain.\nhₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\nh_diff = soln(tₑ).dynamics_h - hₐ\nrmse = sqrt(sum(map(x -> x*x, h_diff)) / length(h_diff))","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Create a gif\nbegin\n frames = 100\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(tₑ).dynamics_h))\n Colorbar(fig[1,2], msh)\n record(fig, \"ice_dynamics_cism.gif\", range(0.0, tₑ; length=frames); framerate = 30) do t\n msh.color = soln(t).dynamics_h\n end\nend","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Ice Dynamics)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"For comparison's sake, we paste the results produced by CISM below. We observe that the error likewise accumulates around the edge of the dome, with more accurate predictions on the interior. We note that our simulation produces slight over-estimates on the interior, but there are further strategies that one can employ to increase accuracy, such as tweaking the error tolerance of the solver, and so on.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Not that since the DEC is based on triangulated meshes, the \"resolution\" of the CISM benchmark and the Decapodes implementation cannot be directly compared. An advantage of the DEC is that we do not need to operate on uniform grids. For example, you could construct a mesh that is finer along the dome edge, where you need more resolution, and coarser as you are farther away from the reach of the ice.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: CISM Results)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We saw in this document how to create performant and accurate simulations in the Decapodes framework, and compared against the CISM library . Although we do not expect to be both more performant and accurate compared to every hand-crafted simulation, Decapodes makes up for this difference in terms of development time, flexibility, and composition. For example, the original implementation of the Decapodes shallow ice model took place over a couple of afternoons.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Since Decapodes targets high-level representations of physics, it is uniquely suited to incorporating knowledge from subject matter experts to increase simulation accuracy. This process does not require an ice dynamics expert to edit physics equations that have already been weaved into solver code.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Further improvements to the Decapodes library are made continuously. We are creating implementations of DEC operators that are constructed and execute faster. And we are in the beginning stages of 3D simulations using the DEC.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Halfar's-model-of-glacial-flow","page":"Glacial Flow","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's model glacial flow using a model of how ice height of a glacial sheet changes over time, from P. Halfar's 1981 paper: \"On the dynamics of the ice sheets\".","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point2, Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nusing Statistics\nPoint2D = Point2{Float64};\nPoint3D = Point3{Float64};","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Defining-the-models","page":"Glacial Flow","title":"Defining the models","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"The first step is to find a suitable equation for our model, and translate it into the Discrete Exterior Calculus. The Exterior Calculus is a generalization of vector calculus, so for low-dimensional spaces, this translation is straightforward. For example, divergence is typically written as (⋆, d, ⋆). Scalar fields are typically interpreted as \"0Forms\", i.e. values assigned to vertices of a mesh.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We use the @decapode macro to interpret the equations. Here, we have equation 2 from Halfar:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"fracpartial hpartial t = frac2n + 2 (fracrho gA)^n fracpartialpartial x(fracpartial hpartial x fracpartial hpartial x ^n-1 h^n+2)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We'll change the term out front to Γ so we can demonstrate composition in a moment.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"In the exterior calculus, we could write the above equations like so:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"partial_t(h) = circ(star d star)(Gammaquad d(h)quad textavg_01d(h)^sharp^n-1 quad textavg_01(h^n+2))","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"avg here is an operator that performs the midpoint rule, setting the value at an edge to be the average of the values at its two vertices.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) * avg₀₁(mag(♯(d(h)))^(n-1)) * avg₀₁(h^(n+2)))\nend\n\nto_graphviz(halfar_eq2)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"And here, a formulation of Glen's law from J.W. Glen's 1958 \"The flow law of ice\".","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"glens_law = @decapode begin\n #Γ::Form0\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nto_graphviz(glens_law)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Composing-models","page":"Glacial Flow","title":"Composing models","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We can use operadic composition to specify how our models come together. In this example, we have two Decapodes, and two quantities that are shared between them.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\ndraw_composition(ice_dynamics_composition_diagram)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"To a apply a composition, we specify which Decapodes to plug into those boxes, and what each calls the corresponding shared variables internally.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\n\nice_dynamics = apex(ice_dynamics_cospan)\nto_graphviz(ice_dynamics)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Provide-a-semantics","page":"Glacial Flow","title":"Provide a semantics","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"To interpret our composed Decapode, we need to specify what Discrete Exterior Calculus to interpret our quantities in. Let's choose the 1D Discrete Exterior Calculus:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics1D = expand_operators(ice_dynamics)\ninfer_types!(ice_dynamics1D, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(ice_dynamics1D, op1_res_rules_1D, op2_res_rules_1D)\n\nto_graphviz(ice_dynamics1D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-a-mesh","page":"Glacial Flow","title":"Define a mesh","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We'll need a mesh to simulate on. Since this is a 1D mesh, we can go ahead and make one right now:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# This is an empty 1D mesh.\ns = EmbeddedDeltaSet1D{Bool, Point2D}()\n\n# 20 vertices along a line, connected by edges.\nadd_vertices!(s, 20, point=Point2D.(range(0, 10_000, length=20), 0))\nadd_edges!(s, 1:nv(s)-1, 2:nv(s))\norient!(s)\n\n# The dual 1D mesh\nsd = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s)\nsubdivide_duals!(sd, Circumcenter())","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-input-data","page":"Glacial Flow","title":"Define input data","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We need initial conditions to use for our simulation.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"n = 3\nρ = 910\ng = 9.8\nA = 1e-16\n\n# Ice height is a primal 0-form, with values at vertices.\n# We choose a distribution that obeys the shallow height and shallow slope conditions.\nh₀ = map(point(s)) do (x,_)\n ((7072-((x-5000)^2))/9e3+2777)/2777e-1\nend\n\n# Visualize initial conditions for ice sheet height.\nlines(map(x -> x[1], point(s)), h₀, linewidth=5)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We need to tell our Decapode which data maps to which symbols. We can wrap up our data like so:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"u₀ = ComponentArray(dynamics_h=h₀)\n\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-functions","page":"Glacial Flow","title":"Define functions","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"In order to solve our equations, we will need numerical linear operators that give meaning to our symbolic operators. In the DEC, there are a handful of operators that one uses to construct all the usual vector calculus operations, namely: ♯, ♭, ∧, d, ⋆. The CombinatorialSpaces.jl library specifies many of these for us.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :♯ => x -> begin\n # This is an implementation of the \"sharp\" operator from the exterior\n # calculus, which takes co-vector fields to vector fields.\n # This could be up-streamed to the CombinatorialSpaces.jl library. (i.e.\n # this operation is not bespoke to this simulation.)\n e_vecs = map(edges(sd)) do e\n point(sd, sd[e, :∂v0]) - point(sd, sd[e, :∂v1])\n end\n neighbors = map(vertices(sd)) do v\n union(incident(sd, v, :∂v0), incident(sd, v, :∂v1))\n end\n n_vecs = map(neighbors) do es\n [e_vecs[e] for e in es]\n end\n map(neighbors, n_vecs) do es, nvs\n sum([nv*norm(nv)*x[e] for (e,nv) in zip(es,nvs)]) / sum(norm.(nvs))\n end\n end\n :mag => x -> norm.(x)\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Generate-the-simulation","page":"Glacial Flow","title":"Generate the simulation","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Now, we have everything we need to generate our simulation:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics1D, dimension=1))\nfₘ = sim(sd, generate)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Pre-compile-and-run","page":"Glacial Flow","title":"Pre-compile and run","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"The first time that you run a function, Julia will pre-compile it, so that later runs will be fast. We'll solve our simulation for a short time span, to trigger this pre-compilation, and then run it.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@info(\"Precompiling Solver\")\nprob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)\nsoln = solve(prob, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")\n\ntₑ = 8_000\n\n# This next run should be fast.\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We can save our solution file in case we want to examine its contents when this Julia session ends.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics1D.jld2\" soln","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Visualize","page":"Glacial Flow","title":"Visualize","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's examine the final conditions:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"fig,ax,ob = lines(map(x -> x[1], point(s)), soln(tₑ).dynamics_h, linewidth=5)\nylims!(ax, extrema(h₀))\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We see that our distribution converges to a more uniform ice height across our domain, which matches our physical intuition.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's create a GIF to examine an animation of these dynamics:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# Create a gif\nbegin\n frames = 100\n fig, ax, ob = lines(map(x -> x[1], point(s)), soln(0).dynamics_h)\n ylims!(ax, extrema(h₀))\n record(fig, \"ice_dynamics1D.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n lines!(map(x -> x[1], point(s)), soln(t).dynamics_h)\n end\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics1D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#2D-Re-interpretation","page":"Glacial Flow","title":"2D Re-interpretation","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"The first, one-dimensional, semantics that we provided to our Decapode restricted the kinds of glacial sheets that we could model. (i.e. We could only look at glacial sheets which were constant along y). We can give our Decapode an alternate semantics, as some physics on a 2-dimensional manifold.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Note that for these physics, we make no adjustments to the underlying \"dimension-agnostic\" Decapode, we only provide a different set of rules for inferring what the type of each quantity is.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics2D = expand_operators(ice_dynamics)\ninfer_types!(ice_dynamics2D)\nresolve_overloads!(ice_dynamics2D)\n\nto_graphviz(ice_dynamics2D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Store-as-JSON","page":"Glacial Flow","title":"Store as JSON","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We quickly demonstrate how to serialize a Decapode to JSON and read it back in:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"write_json_acset(ice_dynamics2D, \"ice_dynamics2D.json\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"You can view the JSON file here.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# When reading back in, we specify that all attributes are \"Symbol\"s.\nice_dynamics2 = read_json_acset(SummationDecapode{Symbol,Symbol,Symbol}, \"ice_dynamics2D.json\")\n# Or, you could choose to interpret the data as \"String\"s.\nice_dynamics3 = read_json_acset(SummationDecapode{String,String,String}, \"ice_dynamics2D.json\")\n\nto_graphviz(ice_dynamics3)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-our-mesh","page":"Glacial Flow","title":"Define our mesh","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"s = triangulated_grid(10_000,10_000,800,800,Point3D)\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-our-input-data","page":"Glacial Flow","title":"Define our input data","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"n = 3\nρ = 910\ng = 9.8\nA = 1e-16\n\n# Ice height is a primal 0-form, with values at vertices.\nh₀ = map(point(s)) do (x,y)\n (7072-((x-5000)^2 + (y-5000)^2)^(1/2))/9e3+10\nend\n\n# Visualize initial condition for ice sheet height.\nmesh(s, color=h₀, colormap=:jet)\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nmsh = mesh!(ax, s, color=h₀, colormap=:jet)\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"u₀ = ComponentArray(dynamics_h=h₀)\n\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-our-functions","page":"Glacial Flow","title":"Define our functions","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :♯ => begin\n sharp_mat = ♯_mat(sd, AltPPSharp())\n x -> sharp_mat * x\n end\n :mag => x -> norm.(x)\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Generate-simulation","page":"Glacial Flow","title":"Generate simulation","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(sd, generate)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Pre-compile-and-run-2D","page":"Glacial Flow","title":"Pre-compile and run 2D","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@info(\"Precompiling Solver\")\n# We run for a short timespan to pre-compile.\nprob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)\nsoln = solve(prob, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"tₑ = 5e13\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Visualize-2D","page":"Glacial Flow","title":"Visualize 2D","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# Final conditions:\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nmsh = mesh!(ax, s, color=soln(tₑ).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"begin\n frames = 100\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1])\n msh = CairoMakie.mesh!(ax, s, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\n Colorbar(fig[1,2], msh)\n record(fig, \"ice_dynamics2D.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n msh.color = soln(t).dynamics_h\n end\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#2-Manifold-in-3D","page":"Glacial Flow","title":"2-Manifold in 3D","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We note that just because our physics is happening on a 2-manifold, (a surface), this doesn't restrict us to the 2D plane. In fact, we can \"embed\" our 2-manifold in 3D space to simulate a glacial sheets spread across the globe.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"s = loadmesh(Icosphere(3, 10_000))\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nwireframe(sd)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"n = 3\nρ = 910\ng = 9.8\nA = 1e-16\n\n# Ice height is a primal 0-form, with values at vertices.\nh₀ = map(point(s)) do (x,y,z)\n (z*z)/(10_000*10_000)\nend\n\n# Visualize initial condition for ice sheet height.\n# There is lots of ice at the poles, and no ice at the equator.\nfig = Figure()\nax = LScene(fig[1,1], scenekw=(lights=[],))\nmsh = CairoMakie.mesh!(ax, s, color=h₀, colormap=:jet)\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"u₀ = ComponentArray(dynamics_h=h₀)\n\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(sd, generate)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"For brevity's sake, we'll skip the pre-compilation cell.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"tₑ = 5e25\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")\n\n# Compare the extrema of the initial and final conditions of ice height.\nextrema(soln(0).dynamics_h), extrema(soln(tₑ).dynamics_h)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"fig = Figure()\nax = LScene(fig[1,1], scenekw=(lights=[],))\nmsh = CairoMakie.mesh!(ax, s, color=soln(tₑ).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"begin\n frames = 200\n fig = Figure()\n ax = LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.mesh!(ax, s, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\n\n Colorbar(fig[1,2], msh)\n # These particular initial conditions diffuse quite quickly, so let's just look at\n # the first moments of those dynamics.\n record(fig, \"ice_dynamics2D_sphere.gif\", range(0.0, tₑ/64; length=frames); framerate = 20) do t\n msh.color = soln(t).dynamics_h\n end\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2DSphere)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"ch/cahn-hilliard/#The-Cahn-Hilliard-Equation","page":"Cahn-Hilliard","title":"The Cahn-Hilliard Equation","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"For this example, Decapodes will model the Cahn-Hilliard equation. This equation describes the evolution of a binary fluid as its two phases separate out into distinct domains. Below is a high resolution preview of this model. Notice how the fluid has separated into distinct regions (blue and red) as well as the presence of a transition region.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"Cahn Hilliard sample\")","category":"page"},{"location":"ch/cahn-hilliard/#Formulating-the-Equation","page":"Cahn-Hilliard","title":"Formulating the Equation","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"We first load in our dependencies","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing Random\nPoint3D = Point3{Float64};\nnothing #hide","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"and then proceed to describe our physics using Decapodes.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"CahnHilliard = @decapode begin\n C::Form0\n (D, γ)::Constant\n ∂ₜ(C) == D * Δ(C.^3 - C - γ * Δ(C))\nend\n\nto_graphviz(CahnHilliard)","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"In this equation C will represent the concentration of the binary fluid, ranging from -1 to 1 to differentiate between different phases. We also have a diffusion constant D and a constant γ whose square root is the length of the transition regions. This formulation of the Cahn-Hilliard equation was drawn from the Wikipedia page on the topic found here.","category":"page"},{"location":"ch/cahn-hilliard/#Loading-the-Data","page":"Cahn-Hilliard","title":"Loading the Data","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"We now generate the mesh information. We'll run the equation on a triangulated grid. We hide the mesh visualization code for clarity.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"s = triangulated_grid(100, 100, 0.5, 0.5, Point3D);\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s);\nsubdivide_duals!(sd, Circumcenter());\nfig = Figure() # hide\nax = CairoMakie.Axis(fig[1,1], aspect=1) # hide\nwf = wireframe!(ax, s; linewidth=1) # hide\nsave(\"CahnHilliard_Rect.png\", fig) # hide\nnothing # hide","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"CahnHilliardRect\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"The Cahn-Hilliard equation starts with a random concentration holding values between -1 and 1. For both D and γ constants we choose 0.5.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"Random.seed!(0)\n\nC = rand(Float64, nv(sd)) * 2 .- 1\nu₀ = ComponentArray(C=C)\nconstants = (D = 0.5, γ = 0.5);\n\nfig = Figure() # hide\nax = CairoMakie.Axis(fig[1,1], aspect=1) # hide\nmsh = CairoMakie.mesh!(ax, s, color=C, colormap=:jet, colorrange=extrema(C)) # hide\nColorbar(fig[1,2], msh)\nsave(\"CahnHilliard_initial.png\", fig) # hide\nnothing # hide","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"Initial conditions\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"We'll now create the simulation code representing the Cahn-Hilliard equation. We pass nothing in the second argument to sim since we have no custom functions to pass in.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"sim = eval(gensim(CahnHilliard))\nfₘ = sim(sd, nothing, DiagonalHodge());","category":"page"},{"location":"ch/cahn-hilliard/#Getting-the-Solution","page":"Cahn-Hilliard","title":"Getting the Solution","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"Now that everything is set up and ready, we can solve the equation. We run the simulation for 200 time units to see the long-term evolution of the fluid. Note we only save the solution at intervals of 0.1 time units in order to reduce the memory-footprint of the solve.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"tₑ = 200\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants)\nsoln = solve(prob, Tsit5(), saveat=0.1);\nsoln.retcode","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"And we can see the result as a gif.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"function create_gif(solution, file_name)\n frames = 200\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1])\n msh = CairoMakie.mesh!(ax, s, color=solution(0).C, colormap=:jet, colorrange=extrema(solution(0).C))\n Colorbar(fig[1,2], msh)\n CairoMakie.record(fig, file_name, range(0.0, tₑ; length=frames); framerate = 15) do t\n msh.color = solution(t).C\n end\nend\ncreate_gif(soln, \"CahnHilliard_Rect.gif\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"CahnHilliardRes\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"nhs/nhs_lite/#Implement-Oceananigans.jl's-NonhydrostaticModel-in-the-Discrete-Exterior-Calculus","page":"NHS","title":"Implement Oceananigans.jl's NonhydrostaticModel in the Discrete Exterior Calculus","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Let's use Decapodes to implement the NonhydrostaticModel from Oceananigans.jl. We will take the opportunity to demonstrate how we can use our \"algebra of model compositions\" to encode certain guarantees on the models we generate. We will use the 2D Turbulence as a guiding example, and use only equations found in the Oceananigans docs to construct our model.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"The full code that generated these results is available in a julia script.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing Downloads\nusing GeometryBasics: Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nPoint3D = Point3{Float64};\nnothing # hide","category":"page"},{"location":"nhs/nhs_lite/#Specify-our-models","page":"NHS","title":"Specify our models","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 1: \"The momentum conservation equation\". This is the first formulation of mutual advection (of v along V, and V along v) that we could find in the exterior calculus.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"momentum = @decapode begin\n (v,V)::DualForm1\n f::Form0\n uˢ::DualForm1\n ∂tuˢ::DualForm1\n p::DualForm0\n b::DualForm0\n ĝ::DualForm1\n Fᵥ::DualForm1\n StressDivergence::DualForm1\n\n ∂ₜ(v) ==\n -ℒ₁(v,v) + 0.5*d(ι₁₁(v,v)) -\n d(ι₁₁(v,V)) + ι₁₂(v,d(V)) + ι₁₂(V,d(v)) -\n (f - ∘(d,⋆)(uˢ)) ∧ᵖᵈ₀₁ v -\n d(p) +\n b ∧ᵈᵈ₀₁ ĝ -\n StressDivergence +\n ∂tuˢ +\n Fᵥ\nend\nto_graphviz(momentum)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Why did we write \"StressDivergence\" instead of ∇⋅τ, as in the linked equation? According to this docs page, the user makes a selection of what model to insert in place of the term ∇⋅τ. For example, in the isotropic case, Oceananigans.jl replaces this term with: ∇⋅τ = νΔv. Thus, we write StressDivergence, and replace this term with a choice of \"turbulence closure\" model. Using the \"constant isotropic diffusivity\" case, we can operate purely in terms of scalar-valued forms.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 2: \"The tracer conservation equation\".","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"tracer_conservation = @decapode begin\n (c,C,F,FluxDivergence)::DualForm0\n (v,V)::DualForm1\n\n ∂ₜ(c) ==\n -1*ι₁₁(v,d(c)) -\n ι₁₁(V,d(c)) -\n ι₁₁(v,d(C)) -\n FluxDivergence +\n F\nend\nto_graphviz(tracer_conservation)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Linear equation of state\" of seawater buoyancy.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"equation_of_state = @decapode begin\n (b,T,S)::DualForm0\n (g,α,β)::Constant\n\n b == g*(α*T - β*S)\nend\nto_graphviz(equation_of_state)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Constant isotropic diffusivity\".","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"isotropic_diffusivity = @decapode begin\n v::DualForm1\n c::DualForm0\n StressDivergence::DualForm1\n FluxDivergence::DualForm0\n (κ,nu)::Constant\n\n StressDivergence == nu*Δᵈ₁(v)\n FluxDivergence == κ*Δᵈ₀(c)\nend\nto_graphviz(isotropic_diffusivity)","category":"page"},{"location":"nhs/nhs_lite/#Compatibility-Guarantees-via-Operadic-Composition","page":"NHS","title":"Compatibility Guarantees via Operadic Composition","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Decapodes composition is formally known as an \"operad algebra\". That means that we don't have to encode our composition in a single undirected wiring diagram (UWD) and then apply it. Rather, we can define several UWDs, compose those, and then apply those. Of course, since the output of oapply is another Decapode, we could perform an intermediate oapply, if that is convenient.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Besides it being convenient to break apart large UWDs into component UWDs, this hierarchical composition can enforce rules on our physical quantities.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"For example:","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"We want all the tracers (salinity, temperature, etc.) in our physics to obey the same conservation equation.\nWe want them to obey the same \"turbulence closure\", which affects their flux-divergence term.\nAt the same time, a choice of turbulence closure doesn't just affect (each of) the flux-divergence terms, it also constrains which stress-divergence is physically valid in the momentum equation.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"We will use our operad algebra to guarantee model compatibility and physical consistency, guarantees that would be burdensome to fit into a one-off type system.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Here, we specify the equations that any tracer obeys:","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"tracer_composition = @relation () begin\n # \"The turbulence closure selected by the user determines the form of ... diffusive flux divergence\"\n turbulence(FD,v,c)\n\n continuity(FD,v,c)\nend\ndraw_composition(tracer_composition)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Let's \"lock in\" isotropic diffusivity by doing an intermediate oapply.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"isotropic_tracer = apex(oapply(tracer_composition, [\n Open(isotropic_diffusivity, [:FluxDivergence, :v, :c]),\n Open(tracer_conservation, [:FluxDivergence, :v, :c])]))\nto_graphviz(isotropic_tracer)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Let's use this building-block tracer physics at the next level. The quotes that appear in this composition diagram appear directly in the Oceananigans.jl docs.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"nonhydrostatic_composition = @relation () begin\n # \"The turbulence closure selected by the user determines the form of stress divergence\"\n # => Note that the StressDivergence term, SD, is shared by momentum and all the tracers.\n momentum(V, v, b, SD)\n\n # \"Both T and S obey the tracer conservation equation\"\n # => Temperature and Salinity both receive a copy of the tracer physics.\n temperature(V, v, T, SD, nu)\n salinity(V, v, S, SD, nu)\n\n # \"Buoyancy is determined from a linear equation of state\"\n # => The b term in momentum is that described by the equation of state here.\n eos(b, T, S)\nend\ndraw_composition(nonhydrostatic_composition)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"isotropic_nonhydrostatic_buoyancy = apex(oapply(nonhydrostatic_composition, [\n Open(momentum, [:V, :v, :b, :StressDivergence]),\n Open(isotropic_tracer, [:continuity_V, :v, :c, :turbulence_StressDivergence, :turbulence_nu]),\n Open(isotropic_tracer, [:continuity_V, :v, :c, :turbulence_StressDivergence, :turbulence_nu]),\n Open(equation_of_state, [:b, :T, :S])]));\nto_graphviz(isotropic_nonhydrostatic_buoyancy)","category":"page"},{"location":"nhs/nhs_lite/#Our-Mesh","page":"NHS","title":"Our Mesh","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"We execute these dynamics on the torus explicitly, instead of using a square with periodic boundary conditions.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"# This is a torus with resolution of its dual mesh similar to that\n# used by Oceananigans (explicitly represented as a torus, not as a\n# square with periodic boundary conditions!)\nDownloads.download(\"https://cise.ufl.edu/~luke.morris/torus.obj\", \"torus.obj\")\ns = EmbeddedDeltaSet2D(\"torus.obj\")\nsd = EmbeddedDeltaDualComplex2D{Bool,Float64,Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nfig = Figure() # hide\nax = CairoMakie.Axis(fig[1,1], aspect=1) # hide\nwf = wireframe!(ax, s; linewidth=1) # hide\nsave(\"NHS_mesh.png\", fig) # hide\nnothing # hide","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"(Image: \"NHS_torus\")","category":"page"},{"location":"nhs/nhs_lite/#Results","page":"NHS","title":"Results","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"In the DEC, vorticity is encoded with d⋆, and speed can be encoded with norm ♯. We can use our operators from CombinatorialSpaces.jl to create our GIFs.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"(Image: Vorticity)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"(Image: Speed)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"poiseuille/poiseuille/#Poissuille-Flow-for-Fluid-Mechanics","page":"Pipe Flow","title":"Poissuille Flow for Fluid Mechanics","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"When modeling a fluid flowing in a pipe, one can ignore the multidimensional structure of the pipe and approximate the system as a 1 dimensional flow along the pipe. The no-slip boundary condition and the geometry of the pipe enter a 1D equation in the form of a resistance term.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# Julia community libraries\nusing CairoMakie\nusing GeometryBasics: Point3\nusing LinearAlgebra\nusing OrdinaryDiffEq\nPoint3D = Point3{Float64}\nnothing # hide","category":"page"},{"location":"poiseuille/poiseuille/#Creating-the-Poiseuille-Equations","page":"Pipe Flow","title":"Creating the Poiseuille Equations","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"The @decapode macro creates the data structure representing the equations of Poiseuille flow. The first block declares variables, the second block defines intermediate terms and the last block is the core equation.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"For these physics, μ̃ represents the negative viscosity per unit area while R represents the drag of the pipe boundary.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Poise = @decapode begin\n P::Form0\n q::Form1\n (R, μ̃ )::Constant\n\n # Laplacian of q for the viscous effect\n Δq == Δ(q)\n # Gradient of P for the pressure driving force\n ∇P == d(P)\n\n # Definition of the time derivative of q\n ∂ₜ(q) == q̇\n\n # The core equation\n q̇ == μ̃ * ∂q(Δq) + ∇P + R * q\nend\n\nPoise = expand_operators(Poise)\ninfer_types!(Poise, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(Poise, op1_res_rules_1D, op2_res_rules_1D)\nto_graphviz(Poise)","category":"page"},{"location":"poiseuille/poiseuille/#Defining-the-Semantics","page":"Pipe Flow","title":"Defining the Semantics","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"In order to solve our equations, we will need numerical linear operators that give meaning to our symbolic operators. The generate function below assigns the necessary matrices as definitions for the symbols. In order to define the viscosity effect correctly we have to identify boundary edges and apply a mask. This is because the DEC has discrete dual cells at the boundaries that need to be handled specially for the viscosity term. We found empirically that if you allow nonzero viscosity at the boundary edges, the flows at the boundaries will be incorrect. You can find the file for boundary conditions here.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using MLStyle\ninclude(\"../boundary_helpers.jl\")\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :∂q => x -> begin\n x[boundary_edges(sd)] .= 0\n x\n end\n :∂ρ => ρ -> begin\n ρ[1] = 0\n ρ[end] = 0\n ρ\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend","category":"page"},{"location":"poiseuille/poiseuille/#A-Single-Pipe-Segment","page":"Pipe Flow","title":"A Single Pipe Segment","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"This simulation can be validated with the Poiseuille equation for a single pipe. First we create a mesh with one pipe segment.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"s = EmbeddedDeltaSet1D{Bool,Point3D}()\nadd_vertices!(s, 2, point=[Point3D(-1, 0, 0), Point3D(+1, 0, 0)])\nadd_edge!(s, 1, 2, edge_orientation=true)\n\nsd = EmbeddedDeltaDualComplex1D{Bool,Float64,Point3D}(s)\nsubdivide_duals!(sd, Circumcenter())\nsd","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using ComponentArrays\nsim = eval(gensim(Poise, dimension=1))\nfₘ = sim(sd, generate)\nq = [2.0]\nP = [10.0, 5.0]\nu = ComponentArray(q=q,P=P)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\nprob = ODEProblem(fₘ, u, (0.0, 10000.0), params)\nsoln = solve(prob, Tsit5())\nsoln.u","category":"page"},{"location":"poiseuille/poiseuille/#A-Linear-Pipe-with-Multiple-Segments","page":"Pipe Flow","title":"A Linear Pipe with Multiple Segments","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"We then move on to a linear sequence of pipe segments. You can visualize this as the discretization of a single long pipe into n segments. First we define the mesh:","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"function linear_pipe(n::Int)\n s = EmbeddedDeltaSet1D{Bool,Point3D}()\n add_vertices!(s, n, point=[Point3D(i, 0, 0) for i in 1:n])\n add_edges!(s, 1:n-1, 2:n, edge_orientation=true)\n sd = EmbeddedDeltaDualComplex1D{Bool,Float64,Point3D}(s)\n subdivide_duals!(sd, Circumcenter())\n sd\nend\n\nsd = linear_pipe(10)\nnothing # hide","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equation. Notice that the equilibrium flow is constant down the length of the pipe. This must be true because of conservation of mass. The segments are all the same length and the total flow in must equal the total flow out of each segment.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Note that we do not generate new simulation code for Poiseuille flow with gensim again. We simply need to provide our new mesh so that our discrete differential operators can be re-instantiated.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"fₘ = sim(sd, generate)\nP = [9,8,7,6,5,4,3,2,1,0]\nq = [5,3,4,2,5,2,8,4,3]\nu = ComponentArray(q=q,P=P)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\nprob = ODEProblem(fₘ, u, (0.0, 10000.0), params)\nsol = solve(prob, Tsit5());\nsol.u","category":"page"},{"location":"poiseuille/poiseuille/#A-Distribution-Network","page":"Pipe Flow","title":"A Distribution Network","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"To model a distribution network, such as residential drinking water system, we will build a binary tree of pipes that at each junction have a bifurcation into two pipes. We expect that the flow will be divided by two at each level of the tree. First we make the mesh.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"function binary_pipe(depth::Int)\n s = EmbeddedDeltaSet1D{Bool,Point3D}()\n add_vertex!(s, point=Point3D(0, 0, 0))\n for n in 1:depth\n for prev_v in vertices(s)[end-2^(n-1)+1:end]\n x, y, _ = s[:point][prev_v]\n vs = add_vertices!(s, 2, point=[Point3D(sgn*3^0.5 + x, y+1, 0)\n for sgn in [1,-1]])\n add_edges!(s, vs, [prev_v,prev_v], edge_orientation=true)\n end\n end\n v = add_vertex!(s, point=Point3D(3^0.5, -1, 0))\n add_edge!(s, 1, v, edge_orientation=true)\n sd = EmbeddedDeltaDualComplex1D{Bool,Float64,Point3D}(s)\n subdivide_duals!(sd, Circumcenter())\n sd\nend\nsd = binary_pipe(2)\nnothing # hide","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"fₘ = sim(sd, generate)\nP = collect(1.0:nv(sd))\nq = fill(5.0, ne(sd))\nu = ComponentArray(q=q,P=P)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\nprob = ODEProblem(fₘ, u, (0.0, 10000.0), params)\nsol = solve(prob, Tsit5())\nsol.u","category":"page"},{"location":"poiseuille/poiseuille/#Multiphysics","page":"Pipe Flow","title":"Multiphysics","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Decapodes really shines when you want to extend or refine your physics. We will change our physics by adding in a term for density of the material and the corresponding changes in pressure. This is not the only formulation for including a dynamic pressure effect into this system. If you can think of a better way to include this effect, we invite you to try it as an exercise!","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Because the pressure is no longer being supplied as a parameter of the system controlled by the operators, we need to introduce a density term and a boundary condition for that density. In this system you can think of forcing a prescribed amount of material per unit time through the openings of the pipe and allowing the flow q and the pressure P to fluctuate. Before we were enforcing a fixed pressure gradient and and letting the flow fluctuate to achieve equilibrium. In the prior model, we were not accounting for the amount of material that had to flow in order to achieve that (flow, pressure) combination.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"The Decapode can be visualized with Graphviz, note that the boundary conditions are explicitly represented in the Decapode as operators that implement a masking operation. This is not consistent with the Diagrammatic Equations in Physics paper. This approach is more directly tied to the computational method and will eventually be replaced with one based on morphisms of diagrams.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"# μ̃ = negative viscosity per unit area\n# R = drag of pipe boundary\n# k = pressure as a function of density\nPoise = @decapode begin\n q::Form1\n (P, ρ)::Form0\n (k, R, μ̃ )::Constant\n\n # Poiseuille Flow\n ∂ₜ(q) == q̇\n ∇P == d(P)\n q̇ == μ̃ * ∂q(Δ(q)) - ∇P + R * q\n \n # Pressure/Density Coupling\n P == k * ρ\n ∂ₜ(ρ) == ρ̇\n ρ_up == ∘(⋆, d, ⋆)(-1 * ∧₀₁(ρ,q)) # advection\n \n # Boundary conditions\n ρ̇ == ∂ρ(ρ_up)\nend\n\nPoise = expand_operators(Poise)\ninfer_types!(Poise, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(Poise, op1_res_rules_1D, op2_res_rules_1D)\nto_graphviz(Poise)","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we can create the mesh and solve the equation.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"sd = linear_pipe(20)\n\nsim = eval(gensim(Poise, dimension=1))\nfunc = sim(sd, generate)\n\nq = [5,3,4,2,5,2,3,4,3, 10,9,8,7,6,5,5,5,5,5]\nρ = [5,3,4,2,5,2,3,4,3, 10,9,8,7,6,5,5,5,5,5,5]\nu = ComponentArray(q=q,ρ=ρ)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\n\nprob = ODEProblem(func, u, (0.0, 10000.0), params)\nsol = solve(prob, Tsit5())\nsol.u","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Notice that the solution contains both a vector of flows and a vector of pressures.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Budko-Sellers-Halfar","page":"Budyko-Sellers-Halfar","title":"Budko-Sellers-Halfar","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\",\"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"In this example, we will compose the Budyko-Sellers 1D energy balance model of the Earth's surface temperature with the Halfar model of glacial dynamics. Note that each of these components models is itself a composition of smaller physical models. In this walkthrough, we will compose them together using the same techniques.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point2\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nPoint2D = Point2{Float64};\nnothing # hide","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We have defined the Halfar ice model in other docs pages, and so will quickly define it here.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nglens_law = @decapode begin\n Γ::Form1\n A::Form1\n (ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\nice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\nhalfar = apex(ice_dynamics_cospan)\n\nto_graphviz(halfar, verbose=false)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We will introduce the Budyko-Sellers energy balance model in more detail. First, let's define the composite physics. We will visualize them all in a single diagram without any composition at first:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"energy_balance = @decapode begin\n (Tₛ, ASR, OLR, HT)::Form0\n (C)::Constant\n\n Tₛ̇ == ∂ₜ(Tₛ) \n\n Tₛ̇ == (ASR - OLR + HT) ./ C\nend\n\nabsorbed_shortwave_radiation = @decapode begin\n (Q, ASR)::Form0\n α::Constant\n\n ASR == (1 .- α) .* Q\nend\n\noutgoing_longwave_radiation = @decapode begin\n (Tₛ, OLR)::Form0\n (A,B)::Constant\n\n OLR == A .+ (B .* Tₛ)\nend\n\nheat_transfer = @decapode begin\n (HT, Tₛ)::Form0\n (D,cosϕᵖ,cosϕᵈ)::Constant\n\n HT == (D ./ cosϕᵖ) .* ⋆(d(cosϕᵈ .* ⋆(d(Tₛ))))\nend\n\ninsolation = @decapode begin\n Q::Form0\n cosϕᵖ::Constant\n\n Q == 450 * cosϕᵖ\nend\n\nto_graphviz(oplus([energy_balance, absorbed_shortwave_radiation, outgoing_longwave_radiation, heat_transfer, insolation]), directed=false)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Now let's compose the Budyko-Sellers model:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_composition_diagram = @relation () begin\n energy(Tₛ, ASR, OLR, HT)\n absorbed_radiation(Q, ASR)\n outgoing_radiation(Tₛ, OLR)\n diffusion(Tₛ, HT, cosϕᵖ)\n insolation(Q, cosϕᵖ)\nend\n\ndraw_composition(budyko_sellers_composition_diagram)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_cospan = oapply(budyko_sellers_composition_diagram,\n [Open(energy_balance, [:Tₛ, :ASR, :OLR, :HT]),\n Open(absorbed_shortwave_radiation, [:Q, :ASR]),\n Open(outgoing_longwave_radiation, [:Tₛ, :OLR]),\n Open(heat_transfer, [:Tₛ, :HT, :cosϕᵖ]),\n Open(insolation, [:Q, :cosϕᵖ])])\n\nbudyko_sellers = apex(budyko_sellers_cospan)\n\n# Save this Decapode as a JSON file\nwrite_json_acset(budyko_sellers, \"budyko_sellers.json\") \n\nto_graphviz(budyko_sellers, verbose=false)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Warming","page":"Budyko-Sellers-Halfar","title":"Warming","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We need to specify physically what it means for these two terms to interact. We will say that ice will diffuse faster as temperature increases, and will pick some coefficients that demonstrate interesting dynamics on short timescales.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"warming = @decapode begin\n Tₛ::Form0\n A::Form1\n\n A == avg₀₁(5.8282*10^(-0.236 * Tₛ)*1.65e7)\n\nend\n\nto_graphviz(warming)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Composition","page":"Budyko-Sellers-Halfar","title":"Composition","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Observe that Decapodes composition is hierarchical. This composition technique is the same as that used in composing each of the Budyko-Sellers and Halfar models.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar_composition_diagram = @relation () begin\n budyko_sellers(Tₛ)\n warming(A, Tₛ)\n halfar(A)\nend\n\ndraw_composition(budyko_sellers_halfar_composition_diagram)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We apply a composition by plugging in a Decapode for each component. We also specify the internal name of the variables to be used in combining.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar_cospan = oapply(budyko_sellers_halfar_composition_diagram,\n [Open(budyko_sellers, [:Tₛ]),\n Open(warming, [:A, :Tₛ]),\n Open(halfar, [:stress_A])])\nbudyko_sellers_halfar = apex(budyko_sellers_halfar_cospan)\n\nto_graphviz(budyko_sellers_halfar)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We can perform type inference to determine what kind of differential form each of our variables are. This is done automatically with the dimension=1 keyword given to gensim, but we will do it in-place for demonstration purposes.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar = expand_operators(budyko_sellers_halfar)\ninfer_types!(budyko_sellers_halfar, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(budyko_sellers_halfar, op1_res_rules_1D, op2_res_rules_1D)\nto_graphviz(budyko_sellers_halfar)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Defining-the-mesh","page":"Budyko-Sellers-Halfar","title":"Defining the mesh","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"These dynamics will occur on a 1-D manifold (a line). Points near +-π/2 will represent points near the North/ South poles. Points near 0 represent those at the equator.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"s = EmbeddedDeltaSet1D{Bool, Point2D}()\nadd_vertices!(s, 100, point=Point2D.(range(-π/2 + π/32, π/2 - π/32, length=100), 0))\nadd_edges!(s, 1:nv(s)-1, 2:nv(s))\norient!(s)\nsd = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s)\nsubdivide_duals!(sd, Circumcenter())","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Define-input-data","page":"Budyko-Sellers-Halfar","title":"Define input data","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We need to supply initial conditions to our model. We will use synthetic data here.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# This is a primal 0-form, with values at vertices.\ncosϕᵖ = map(x -> cos(x[1]), point(s))\n\n# This is a dual 0-form, with values at edge centers.\ncosϕᵈ = map(edges(s)) do e\n (cos(point(s, src(s, e))[1]) + cos(point(s, tgt(s, e))[1])) / 2\nend\n\nα₀ = 0.354\nα₂ = 0.25\nα = map(point(s)) do ϕ\n α₀ + α₂*((1/2)*(3*ϕ[1]^2 - 1))\nend\nA = 210\nB = 2\nf = 0.70\nρ = 1025\ncw = 4186\nH = 70\nC = map(point(s)) do ϕ\n f * ρ * cw * H\nend\nD = 0.6\n\n# Isothermal initial conditions:\nTₛ₀ = map(point(s)) do ϕ\n 15\nend\n\n# Visualize initial condition for temperature.\nlines(map(x -> x[1], point(s)), Tₛ₀)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"n = 3\nρ = 910\ng = 9.8\n\n# Ice height is a primal 0-form, with values at vertices.\nh₀ = map(point(s)) do (x,_)\n (((x)^2)+2.5) / 1e3\nend\n\n# Visualize initial condition for ice sheet height.\nlines(map(x -> x[1], point(s)), h₀)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# Store these values to be passed to the solver.\nu₀ = ComponentArray(Tₛ=Tₛ₀, halfar_dynamics_h=h₀)\n\nconstants_and_parameters = (\n budyko_sellers_absorbed_radiation_α = α,\n budyko_sellers_outgoing_radiation_A = A,\n budyko_sellers_outgoing_radiation_B = B,\n budyko_sellers_energy_C = C,\n budyko_sellers_diffusion_D = D,\n budyko_sellers_cosϕᵖ = cosϕᵖ,\n budyko_sellers_diffusion_cosϕᵈ = cosϕᵈ,\n halfar_n = n,\n halfar_stress_ρ = ρ,\n halfar_stress_g = g)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Symbols-to-functions","page":"Budyko-Sellers-Halfar","title":"Symbols to functions","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"The symbols along edges in our Decapode must be mapped to executable functions. In the Discrete Exterior Calculus, all our operators are defined as relations between points, lines, and triangles on meshes known as simplicial sets. Thus, DEC operators are re-usable across any simplicial set.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :♯ => x -> begin\n # This is an implementation of the \"sharp\" operator from the exterior\n # calculus, which takes co-vector fields to vector fields.\n # This could be up-streamed to the CombinatorialSpaces.jl library. (i.e.\n # this operation is not bespoke to this simulation.)\n e_vecs = map(edges(sd)) do e\n point(sd, sd[e, :∂v0]) - point(sd, sd[e, :∂v1])\n end\n neighbors = map(vertices(sd)) do v\n union(incident(sd, v, :∂v0), incident(sd, v, :∂v1))\n end\n n_vecs = map(neighbors) do es\n [e_vecs[e] for e in es]\n end\n map(neighbors, n_vecs) do es, nvs\n sum([nv*norm(nv)*x[e] for (e,nv) in zip(es,nvs)]) / sum(norm.(nvs))\n end\n end\n :mag => x -> norm.(x)\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Simulation-generation","page":"Budyko-Sellers-Halfar","title":"Simulation generation","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"From our Decapode, we automatically generate a finite difference method solver that performs explicit time-stepping to solve our system of multiphysics equations.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"sim = eval(gensim(budyko_sellers_halfar, dimension=1))\nfₘ = sim(sd, generate)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Run-simulation","page":"Budyko-Sellers-Halfar","title":"Run simulation","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We wrap our simulator and initial conditions and solve them with the stability-detection and time-stepping methods provided by DifferentialEquations.jl .","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"tₑ = 1e6\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We can save the solution file to examine later.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"@save \"budyko_sellers_halfar.jld2\" soln","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Visualize","page":"Budyko-Sellers-Halfar","title":"Visualize","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for temperature:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"lines(map(x -> x[1], point(s)), soln(tₑ).Tₛ)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for ice height:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"lines(map(x -> x[1], point(s)), soln(tₑ).halfar_dynamics_h)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"begin\n# Initial frame\nframes = 100\nfig = Figure()\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s))))\nylims!(ax1, extrema(soln(tₑ).Tₛ))\nax1.xlabel = \"Line plot of temperature from North to South pole, every $(tₑ/frames) time units\"\nLabel(fig[1,1,Top()], \"Surface temperature, Tₛ, [C°]\")\n\n# Animation\nrecord(fig, \"budyko_sellers_halfar_T.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n lines!(fig[1,1], map(x -> x[1], point(s)), soln(t).Tₛ)\nend\nend\n\nbegin\n# Initial frame\nframes = 100\nfig = Figure()\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s))))\nylims!(ax1, extrema(soln(tₑ).halfar_dynamics_h))\nax1.xlabel = \"Line plot of temperature from North to South pole, every $(tₑ/frames) time units\"\nLabel(fig[1,1,Top()], \"Ice height, h\")\n\n# Animation\nrecord(fig, \"budyko_sellers_halfar_h.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n lines!(fig[1,1], map(x -> x[1], point(s)), soln(t).halfar_dynamics_h)\nend\nend","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_Temperature)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_IceHeight)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"klausmeier/klausmeier/#Klausmeier","page":"Klausmeier","title":"Klausmeier","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"include(joinpath(Base.@__DIR__, \"..\" , \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Somaliland Vegetation)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"
\n
One of the first aerial photographs of British Somaliland (now Somaliland) investigated by W.A. Macfadyen in his 1950 \"Vegetation Patterns in the Semi-Desert Plains of British Somaliland\" [1]. From this point of view, Macfadyen's \"vegetation arcs\" are plainly visible.
\n
","category":"page"},{"location":"klausmeier/klausmeier/#Background","page":"Klausmeier","title":"Background","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"From aerial photographs in the late 1940s, British ecologist W.A. Macfadyen discovered that vegetation in semi-arid environments often grows in striping patterns, but was unaware of the exact mechanism that causes them. What is especially striking about these \"vegetation arcs\" is that these stripes appear to climb uphill, with denser plant growth at the leading edge of these traveling waves. Much like how the Mandelbrot set and other interesting fractal patterns can arise from simple sets of rules, these vegetation dynamics can be explained by simple sets of partial differential equations.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The Klausmeier model, given by Christopher Klausmeier in his 1999 paper Regular and Irregular Patterns in Semiarid Vegetation[2], models such dynamics. Although Macfadyen had discovered these vegetation patterns 50s years prior[1,3], defining these dynamics through accessible and physically-meaningful PDEs proved a catalyst for further research. At the time of writing, Klausmeier's paper has been cited 594 times.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"In this document, we will use Decapodes to formally represent these equations. Moreover, we will demonstrate how one can automatically generate simulation that reproduces the dynamics given by a scientist, simply by reading in the equations given in their original publication.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The lofty goal of this document, and of Decapodes itself, is that through both explicitly representing a model - such as Klausmeier's - and handling the generation of simulation code, we can amplify its accessibility and composability, and ultimately spur further research. Lets start by using Decapodes.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier GIF)","category":"page"},{"location":"klausmeier/klausmeier/#using-Decapodes","page":"Klausmeier","title":"using Decapodes","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Load Dependencies\nusing CairoMakie\nusing Catlab\nusing CombinatorialSpaces\nusing ComponentArrays\nusing Decapodes\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Distributions\nusing GeometryBasics: Point2\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nPoint2D = Point2{Float64}\nnothing # hide","category":"page"},{"location":"klausmeier/klausmeier/#Model-Representation","page":"Klausmeier","title":"Model Representation","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The Klausmeier model consists of two parts: one governing plant growth (phytodynamics), and one governing hydrodynamics. The differential operator Δ represents the diffusion of vegetation, and the \"Lie derivative\" operator ℒ represents the change of water in a direction. In this case, the flow of water downhill, \"dX\".","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Decapodes are written in the language of the Exterior Calculus, which generalizes Vector Calculus. For us, that means that we get to specify whether a physical quantity should valued at points in our domain (i.e. primal and dual Form0s), or whether they are more vector-like quantities, that should be valued along edges in our domain (i.e. primal and dual Form1s). In this case, water density w and vegetation density n are both Form0s, while dX, the gradient of our hill, is a Form1.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# See Klausmeier Equation 2.a\nHydrodynamics = @decapode begin\n (n,w)::DualForm0\n dX::Form1\n (a,ν)::Constant\n\n ∂ₜ(w) == a - w - w * n^2 + ν * L(dX, w)\nend\n\n# See Klausmeier Equation 2.b\nPhytodynamics = @decapode begin\n (n,w)::DualForm0\n m::Constant\n\n ∂ₜ(n) == w * n^2 - m*n + Δ(n)\nend\nnothing # hide","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Now that we have our two component models, we can specify a means of composing them via a composition pattern.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Specify Composition\ncompose_klausmeier = @relation () begin\n phyto(N, W)\n hydro(N, W)\nend\n\ndraw_composition(compose_klausmeier)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We apply our composition pattern by plugging in component Decapodes, and specifying which internal quantities to share along edges. Decapodes are formalized via the field of Applied Category Theory. A practical consequence here is that we can view a Decapode as a sort of computation graph.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Apply Composition\nklausmeier_cospan = oapply(compose_klausmeier,\n [Open(Phytodynamics, [:n, :w]),\n Open(Hydrodynamics, [:n, :w])])\nKlausmeier = apex(klausmeier_cospan)\nto_graphviz(Klausmeier)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"With our model now explicitly represented, we have everything we need to automatically generate simulation code. We could write this to an intermediate file and use it later, or we can go ahead and evaluate the code in this session.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"sim = eval(gensim(Klausmeier, dimension=1))","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We now need a mesh to define our domain. In the 2D case, our CombinatorialSpaces library can read in arbitrary .OBJ files. In 1D, it is often simpler to just generate a mesh on the fly. Since we are running our physics on a circle - i.e periodic boundaries - we will use a simple function that generates it.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We will visualize the mesh embedded in two dimensions here, but in later visualizations, we can represent it as a periodic line.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Define Mesh\nfunction circle(n, c)\n s = EmbeddedDeltaSet1D{Bool, Point2D}()\n map(range(0, 2pi - (pi/(2^(n-1))); step=pi/(2^(n-1)))) do t\n add_vertex!(s, point=Point2D(cos(t),sin(t))*(c/2pi))\n end\n add_edges!(s, 1:(nv(s)-1), 2:nv(s))\n add_edge!(s, nv(s), 1)\n sd = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s)\n subdivide_duals!(sd, Circumcenter())\n s,sd\nend\ns,sd = circle(9, 500)\n\nscatter(sd[:point])","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We discretize our differential operators using the Discrete Exterior Calculus. The DEC is an elegant way of building up more complex differential operators from simpler ones. To demonstrate, we will define the Δ operator by building it up with matrix multiplication of simpler operators. Since most operators in the DEC are matrices, most simulations consist mainly of matrix-vector multiplications, and are thus very fast.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"If this code seems too low level, do not worry. Decapodes defines and caches for you many differential operators behind the scenes, so you do not have to worry about defining your own.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"lap_mat = dec_hodge_star(1,sd) * dec_differential(0,sd) * dec_inv_hodge_star(0,sd) * dec_dual_derivative(0,sd)\n\nfunction generate(sd, my_symbol; hodge=DiagonalHodge())\n op = @match my_symbol begin\n :Δ => x -> begin\n lap_mat * x\n end\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's pass our mesh and methods of generating operators to our simulation code.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Instantiate Simulation\nfₘ = sim(sd, generate, DiagonalHodge())","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"With our simulation now ready, let's specify initial data to pass to it. We'll define them with plain Julia code.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The most interesting parameter here is our \"downhill gradient\" dX. This parameter defines how steep our slope is. Since our mesh is a circle, and we are setting dX to a constant value, this means that \"downhill\" always points counter-clockwise. Essentially, this is an elegant way of encoding an infinite hill.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Define Initial Conditions\nn_dist = Normal(pi)\nn = [pdf(n_dist, t)*(√(2pi))*7.2 + 0.08 - 5e-2 for t in range(0,2pi; length=ne(sd))]\n\nw_dist = Normal(pi, 20)\nw = [pdf(w_dist, t) for t in range(0,2pi; length=ne(sd))]\n\ndX = sd[:length]\n\nu₀ = ComponentArray(N = n, W = w, hydro_dX = dX)\n\ncs_ps = (phyto_m = 0.45,\n hydro_a = 0.94,\n hydro_ν = 182.5)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's execute our simulation.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Run Simulation\ntₑ = 300.0\nprob = ODEProblem(fₘ, u₀, (0.0, tₑ), cs_ps)\nsol = solve(prob, Tsit5(), saveat=0.1, save_idxs=[:N, :W])\nsol.retcode","category":"page"},{"location":"klausmeier/klausmeier/#Animation","page":"Klausmeier","title":"Animation","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's perform some basic visualization and analysis of our results to verify our dynamics.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"n = sol(0).N\nnₑ = sol(tₑ).N\nw = sol(0).W\nwₑ = sol(tₑ).W\nnothing # hide","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Animate dynamics\nfunction save_dynamics(form_name, framerate, filename)\n time = Observable(0.0)\n ys = @lift(getproperty(sol($time), form_name))\n xcoords = [0, accumulate(+, sd[:length])[1:end-1]...]\n fig = lines(xcoords, ys, color=:green, linewidth=4.0,\n colorrange=extrema(getproperty(sol(0), form_name));\n axis = (; title = @lift(\"Klausmeier $(String(form_name)) at $($time)\")))\n timestamps = range(0, tₑ, step=1)\n record(fig, filename, timestamps; framerate=framerate) do t\n time[] = t\n end\nend\nsave_dynamics(:N, 20, \"klausmeier.gif\")","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We can observe a few interesting phenomena that we wanted to capture:","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The vegetation density bands move uphill in traveling waves.\nThe leading edge of the waves is denser than the rest of the band.\nOver time, the periodicity of the vegetation bands stabilizes.\nThe distribution naturally emerges, despite the initial distribution is a simple normal distribution.\nThis is evidence against the real-world theory that these vegetation contours are the result of an initial (man-made) distribution.","category":"page"},{"location":"klausmeier/klausmeier/#Conclusion","page":"Klausmeier","title":"Conclusion","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Due to the ease of composition of Decapodes, representing the Klausmeier model opens up many areas for future work. For example, we can now compose these dynamics with a model of temperature dynamics informed by the Budyko-Sellers model. We can take advantage of the fact that the Lie derivative generalizes partial derivatives, and model the flow of water according to any vector field. Or, we can extend this model by composing it with a model that can recreate the so-called \"leopard pattern\" of vegetation, such as an \"Interaction-Dispersion\" model of vegetation dynamics given by Lejeune et al[4].","category":"page"},{"location":"klausmeier/klausmeier/#References","page":"Klausmeier","title":"References","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[1] W. A. Macfadyen, “Vegetation Patterns in the Semi-Desert Plains of British Somaliland,” The Geographical Journal, vol. 116, no. 4/6, p. 199, Oct. 1950, doi: 10.2307/1789384.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[2] C. A. Klausmeier, “Regular and Irregular Patterns in Semiarid Vegetation,” Science, vol. 284, no. 5421, pp. 1826–1828, Jun. 1999, doi: 10.1126/science.284.5421.1826.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[3] W. A. Macfadyen, “Soil and Vegetation in British Somaliland,” Nature, vol. 165, no. 4186, Art. no. 4186, Jan. 1950, doi: 10.1038/165121a0.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[4] O. Lejeune and M. Tlidi, “A Model for the Explanation of Vegetation Stripes (Tiger Bush),” Journal of Vegetation Science, vol. 10, no. 2, pp. 201–208, 1999, doi: 10.2307/3237141.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"grigoriev/grigoriev/#Halfar's-model-of-glacial-flow","page":"Grigoriev Ice Cap","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"include(joinpath(Base.@__DIR__, \"..\" , \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's model glacial flow using a model of how ice height of a glacial sheet changes over time, from P. Halfar's 1981 paper: \"On the dynamics of the ice sheets\".","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's run the Halfar shallow ice / shallow slope model on some \"real world\" data for ice thickness. Van Tricht et al. in their 2023 communication Measuring and modelling the ice thickness of the Grigoriev ice cap (Kyrgyzstan) and comparison with global dataset published ice thickness data on an ice cap and stored their data in a TIF. In this document, we will demonstrate how to parse such data and execute a Decapodes model on these initial conditions.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"For the parameters to Glen's law, we will use those used in the Community Ice Sheet Model benchmark. Of course, the parameters of this Kyrgyzstani ice cap likely differ from these by quite some amount, but they are a good place to start. Further, this ice cap does not satisfy the \"shallow slope\" assumption across the entire domain.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing FileIO \nusing GeometryBasics: Point2\nusing Interpolations\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64};\nnothing # hide","category":"page"},{"location":"grigoriev/grigoriev/#Loading-a-Scientific-Dataset","page":"Grigoriev Ice Cap","title":"Loading a Scientific Dataset","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The ice thickness data is stored in a TIF that can be downloaded here. We have downloaded it locally, and load it using basic FileIO.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"file_name = \"Icethickness_Grigoriev_ice_cap_2021.tif\"\nice_thickness_tif = load(file_name)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"This data may visually appear to be a binary mask but that is only because values with no ice are set to -Inf. We will account for this when interpolate our data.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"We use the Interpolations.jl library to interpolate this dataset:","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# Taking the coordinates to be from the extrema of the measured points:\nconst MIN_X = 4648894.5\nconst MAX_X = 4652179.7\nconst MIN_Y = 243504.5\nconst MAX_Y = 245599.8\nice_coords = (range(MIN_X, MAX_X, length=size(ice_thickness_tif,1)),\n range(MIN_Y, MAX_Y, length=size(ice_thickness_tif,2)))\n\n# Note that the TIF is set to -floatmax(Float32) where there is no ice.\n# For our purposes, this is equivalent to 0.0.\nice_interp = LinearInterpolation(ice_coords, Float32.(ice_thickness_tif))","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"To use this interpolating object ice_interp, we can simply query it for the value at some coordinates: ice_interp(x,y).","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's generate a triangulated grid located at the appropriate coordinates:","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# Specify a resolution:\nRES_Y = (MAX_Y-MIN_Y)/30.0\nRES_X = RES_Y\n\n# Generate the mesh with appropriate dimensions and resolution:\ns = triangulated_grid(MAX_X-MIN_X, MAX_Y-MIN_Y, RES_X, RES_Y, Point3D)\n\n# Shift it into place:\ns[:point] = map(x -> x + Point3D(MIN_X, MIN_Y, 0), s[:point])\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s)\nsave(\"Grigoriev_IceMesh.png\", fig)\nnothing # hide","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: \"Grigoriev_IceMesh\")","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The coordinates of a vertex are stored in sd[:point]. Let's use our interpolator to assign ice thickness values to each vertex in the mesh:","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# These are the values used by the CISM benchmark:\nn = 3\nρ = 910\ng = 9.8101\nA = fill(1e-16, ne(sd))\n\nh₀ = map(sd[:point]) do (x,y,_)\n tif_val = ice_interp(x,y)\n # Accommodate for the -∞'s that encode \"no ice\".\n tif_val < 0.0 ? 0.0 : tif_val\nend\n\n# Store these values to be passed to the solver.\nu₀ = ComponentArray(h=h₀, stress_A=A)\nconstants_and_parameters = (n = n, \n stress_ρ = ρ,\n stress_g = g, \n stress_A = A)\nnothing # hide","category":"page"},{"location":"grigoriev/grigoriev/#Defining-and-Composing-Models","page":"Grigoriev Ice Cap","title":"Defining and Composing Models","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"For exposition on this Halfar Decapode, see our Glacial Flow docs page. Otherwise, you may skip ahead to the next section.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nglens_law = @decapode begin\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(h,Γ,n)\n stress(Γ,n)\nend\n\nice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:h,:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\n\nice_dynamics = apex(ice_dynamics_cospan)\nto_graphviz(ice_dynamics)","category":"page"},{"location":"grigoriev/grigoriev/#Define-our-functions","page":"Grigoriev Ice Cap","title":"Define our functions","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :mag => x -> norm.(x)\n :♯ => begin\n sharp_mat = ♯_mat(sd, AltPPSharp())\n x -> sharp_mat * x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend","category":"page"},{"location":"grigoriev/grigoriev/#Generate-simulation","page":"Grigoriev Ice Cap","title":"Generate simulation","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"sim = eval(gensim(ice_dynamics, dimension=2))\nfₘ = sim(sd, generate)","category":"page"},{"location":"grigoriev/grigoriev/#Run","page":"Grigoriev Ice Cap","title":"Run","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"tₑ = 10\n\n@info(\"Solving Grigoriev Ice Cap\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")\n\n@save \"grigoriev.jld2\" soln","category":"page"},{"location":"grigoriev/grigoriev/#Results-and-Discussion","page":"Grigoriev Ice Cap","title":"Results and Discussion","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# Visualize the initial conditions.\nfunction plot_ic()\n f = Figure()\n ax = CairoMakie.Axis(f[1,1],\n title=\"Grigoriev Ice Cap Initial Thickness [m]\",\n xticks = range(MIN_X, MAX_X; length=5),\n yticks = range(MIN_Y, MAX_Y; length=5))\n msh = mesh!(ax, s, color=soln(0.0).h, colormap=:jet)\n Colorbar(f[1,2], msh)\n f\nend\nf = plot_ic()\nsave(\"grigoriev_ic.png\", f)\n\n# Visualize the final conditions.\nfunction plot_fc()\n f = Figure()\n ax = CairoMakie.Axis(f[1,1],\n title=\"Grigoriev Ice Cap Final Thickness [m]\",\n xticks = range(MIN_X, MAX_X; length=5),\n yticks = range(MIN_Y, MAX_Y; length=5))\n msh = mesh!(ax, s, color=soln(tₑ).h, colormap=:jet)\n Colorbar(f[1,2], msh)\n f\nend\nf = plot_fc()\nsave(\"grigoriev_fc.png\", f)\n\n# Create a gif\nfunction save_dynamics(save_file_name)\n time = Observable(0.0)\n h = @lift(soln($time).h)\n f = Figure()\n ax = CairoMakie.Axis(f[1,1], title = @lift(\"Grigoriev Ice Cap Dynamic Thickness [m] at time $($time)\"))\n gmsh = mesh!(ax, s, color=h, colormap=:jet,\n colorrange=extrema(soln(tₑ).h))\n #Colorbar(f[1,2], gmsh, limits=extrema(soln(tₑ).h))\n Colorbar(f[1,2], gmsh)\n timestamps = range(0, tₑ, step=1e-1)\n record(f, save_file_name, timestamps; framerate = 15) do t\n time[] = t\n end\nend\nsave_dynamics(\"grigoriev.gif\")","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"We observe the usual Halfar model phenomena of ice \"melting\". Note that since the \"shallow slope\" approximation does not hold on the boundaries (due to the so-called \"ice cliffs\" described in the Van Tricht et al. paper), we do not expect the \"creep\" effect to be physical in this region of the domain. Rather, the Halfar model's predictive power is tuned for the interiors of ice caps and glaciers. Note that we also assume here that the bedrock that the ice rests on is flat. We may in further documents demonstrate how to use topographic data from Digital Elevation Models to inform the elevation of points in the mesh itself.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_ICs)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_FCs)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_Dynamics)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"overview/overview/#Introduction-to-Decapodes","page":"Overview","title":"Introduction to Decapodes","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Discrete Exterior Calculus Applied to Partial and Ordinary Differential Equations (Decapodes) is a diagrammatic language used to express systems of ordinary and partial differential equations. Decapodes provides a visual framework for understanding the coupling between variables within a PDE or ODE system, and a combinatorial data structure for working with them. Below, we provide a high-level overview of how Decapodes can be generated and interpreted.","category":"page"},{"location":"overview/overview/#Your-First-Decapode","page":"Overview","title":"Your First Decapode","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"In the Decapodes graphical paradigm, nodes represent variables and arrows represent operators which relate variables to each other. Since Decapodes applies this diagrammatic language specifically to the Discrete Exterior Calculus (DEC), variables are typed by the dimension and orientation of the information they contain. So a variable of type Form0 will be the 0-dimensional data points defined the vertices of a mesh. Similarly, Form1 will be values stored on edges of the mesh and Form2 will be values stored on the surfaces of the mesh.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Below, we provide a Decapode with just a single variable C and display it.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using Catlab\nusing Decapodes\nusing DiagrammaticEquations\n\nVariable = @decapode begin\n C::Form0\nend;\n\nto_graphviz(Variable)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The resulting diagram contains a single node, showing the single variable in this system. We can add a second variable:","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"TwoVariables = @decapode begin\n C::Form0\n dC::Form1\nend;\n\nto_graphviz(TwoVariables)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We can also add a relationship between them. In this case, we make an equation which states that dC is the derivative of C:","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Equation = @decapode begin\n C::Form0\n dC::Form1\n\n dC == d(C)\nend;\n\nto_graphviz(Equation)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Here, the two nodes represent the two variables, and the arrow between them shows how they are related by the derivative.","category":"page"},{"location":"overview/overview/#A-Little-More-Complicated","page":"Overview","title":"A Little More Complicated","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that we've seen how to construct a simple equation, it's time to move on to some actual PDE systems! One classic PDE example is the diffusion equation. This equation states that the change of concentration at each point is proportional to the Laplacian of the concentration.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Diffusion = @decapode begin\n (C, Ċ)::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\n\n # Diffusion equation\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\n\nend;\n\nto_graphviz(Diffusion)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The resulting Decapode shows the relationships between the three variables with the triangle diagram. Note that these diagrams are automatically layed-out by Graphviz.","category":"page"},{"location":"overview/overview/#Bring-in-the-Dynamics","page":"Overview","title":"Bring in the Dynamics","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that we have a reasonably complex PDE, we can demonstrate some of the developed tooling for actually solving the PDE. Currently, the tooling will automatically generate an explicit method for solving the system (using DifferentialEquations.jl to handle time-stepping and instability detection).","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Torus_30x10 is a default mesh that is downloaded via Artifacts.jl when a user installs CombinatorialSpaces.jl. If we wanted, we could also instantiate any .obj file of triangulated faces as a simplicial set although we do not here.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We will also upload a non-periodic mesh for the sake of visualization, as well as a mapping between the points on the periodic and non-periodic meshes.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using CairoMakie\nusing CombinatorialSpaces\n\nplot_mesh = loadmesh(Rectangle_30x10())\nperiodic_mesh = loadmesh(Torus_30x10())\npoint_map = loadmesh(Point_Map())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nwireframe!(ax, plot_mesh)\nfig","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now we create a function which links the names of functions used in the Decapode to their implementations. Note that many DEC operators are already defined for you.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"As an example, we chose to define k as a function that multiplies an input by 0.05. We could have alternately chosen to represent k as a Constant that we multiply by in the Decapode itself.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We then compile the simulation by using gen_sim and create functional simulation by calling the evaluated sim with the mesh and our generate function.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using MLStyle\n\nfunction generate(sd, my_symbol; hodge=DiagonalHodge())\n op = @match my_symbol begin\n :k => x -> 0.05*x\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend\n\nsim = eval(gensim(Diffusion))\nfₘ = sim(periodic_mesh, generate, DiagonalHodge())","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We go ahead and set up our initial conditions for this problem. In this case we generate a Gaussian and apply it to our mesh.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using Distributions\nc_dist = MvNormal([7, 5], [1.5, 1.5])\nc = [pdf(c_dist, [p[1], p[2]]) for p in periodic_mesh[:point]]\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nmesh!(ax, plot_mesh; color=c[point_map])\nfig","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Finally, we solve this PDE problem using the Tsit5() solver provided by DifferentialEquations.jl.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using LinearAlgebra\nusing ComponentArrays\nusing OrdinaryDiffEq\n\nu₀ = ComponentArray(C=c)\n\nprob = ODEProblem(fₘ, u₀, (0.0, 100.0))\nsol = solve(prob, Tsit5());\nsol.retcode","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that the simulation has succeeded we can plot out our results with CairoMakie.jl.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"# Plot the result\ntimes = range(0.0, 100.0, length=150)\ncolors = [sol(t).C[point_map] for t in times]\n\n# Initial frame\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\npmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))\nColorbar(fig[1,2], pmsh)\nframerate = 30\n\n# Animation\nrecord(fig, \"diffusion.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n pmsh.color = sol(t).C[point_map]\nend","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"(Image: Your first Decapode!)","category":"page"},{"location":"overview/overview/#Merging-Multiple-Physics","page":"Overview","title":"Merging Multiple Physics","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that we've seen the basic pipeline, it's time for a more complex example that demonstrates some of the benefits reaped from using Catlab.jl as the backend to our data structures. In this example, we will take two separate physics (diffusion and advection), and combine them together using a higher-level composition pattern.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We begin by defining the three systems we need. The first two systems are the relationships between concentration and flux under diffusion and advection respectively. The third is the relationship between the two fluxes and the change of concentration under superposition of fluxes.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Diffusion = @decapode begin\n C::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\nend\n\nAdvection = @decapode begin\n C::Form0\n ϕ::Form1\n V::Form1\n\n ϕ == ∧₀₁(C,V)\nend\n\nSuperposition = @decapode begin\n (C, Ċ)::Form0\n (ϕ, ϕ₁, ϕ₂)::Form1\n\n ϕ == ϕ₁ + ϕ₂\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\nend\nnothing # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The diffusion Decapode.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"to_graphviz(Diffusion) # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The advection Decapode.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"to_graphviz(Advection) # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"And the superposition Decapode.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"to_graphviz(Superposition) # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Next, we define the pattern of composition which we want to compose these physics under. This pattern of composition is described by an undirected wiring diagram, which has the individual physics as nodes and the shared variables as the small junctions.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"compose_diff_adv = @relation (C, V) begin\n diffusion(C, ϕ₁)\n advection(C, ϕ₂, V)\n superposition(ϕ₁, ϕ₂, ϕ, C)\nend\n\ndraw_composition(compose_diff_adv)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"After this, the physics can be composed as follows:","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"DiffusionAdvection_cospan = oapply(compose_diff_adv,\n [Open(Diffusion, [:C, :ϕ]),\n Open(Advection, [:C, :ϕ, :V]),\n Open(Superposition, [:ϕ₁, :ϕ₂, :ϕ, :C])])\nDiffusionAdvection = apex(DiffusionAdvection_cospan)\n\nto_graphviz(DiffusionAdvection)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Similar to before, this physics can be compiled and executed. Note that this process now requires another value to be defined, namely the velocity vector field. We do this using a custom operator called flat_op. This operator is basically the flat operator from CombinatorialSpaces.jl, but specialized to account for the periodic mesh.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We could instead represent the domain as the surface of an object with equivalent boundaries in 3D.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"function closest_point(p1, p2, dims)\n p_res = collect(p2)\n for i in 1:length(dims)\n if dims[i] != Inf\n p = p1[i] - p2[i]\n f, n = modf(p / dims[i])\n p_res[i] += dims[i] * n\n if abs(f) > 0.5\n p_res[i] += sign(f) * dims[i]\n end\n end\n end\n Point3{Float64}(p_res...)\nend\n\nfunction flat_op(s::AbstractDeltaDualComplex2D, X::AbstractVector; dims=[Inf, Inf, Inf])\n tri_map = Dict{Int,Int}(triangle_center(s,t) => t for t in triangles(s))\n\n map(edges(s)) do e\n p = closest_point(point(s, tgt(s,e)), point(s, src(s,e)), dims)\n e_vec = (point(s, tgt(s,e)) - p) * sign(1,s,e)\n dual_edges = elementary_duals(1,s,e)\n dual_lengths = dual_volume(1, s, dual_edges)\n mapreduce(+, dual_edges, dual_lengths) do dual_e, dual_length\n X_vec = X[tri_map[s[dual_e, :D_∂v0]]]\n dual_length * dot(X_vec, e_vec)\n end / sum(dual_lengths)\n end\nend","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using LinearAlgebra\nusing MLStyle\n\nfunction generate(sd, my_symbol; hodge=DiagonalHodge())\n op = @match my_symbol begin\n :k => x -> 0.05*x\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend\n\nsim = eval(gensim(DiffusionAdvection))\nfₘ = sim(periodic_mesh, generate, DiagonalHodge())\n\nvelocity(p) = [-0.5, -0.5, 0.0]\nv = flat_op(periodic_mesh, DualVectorField(velocity.(periodic_mesh[triangle_center(periodic_mesh),:dual_point])); dims=[30, 10, Inf])\n\nu₀ = ComponentArray(C=c,V=v)\n\nprob = ODEProblem(fₘ, u₀, (0.0, 100.0))\nsol = solve(prob, Tsit5());\n\n# Plot the result\ntimes = range(0.0, 100.0, length=150)\ncolors = [sol(t).C[point_map] for t in times]\n\n# Initial frame\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\npmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))\nColorbar(fig[1,2], pmsh)\nframerate = 30\n\n# Animation\nrecord(fig, \"diff_adv.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n pmsh.color = sol(t).C[point_map]\nend","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"(Image: Your first composed Decapode!)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"halmo/halmo/#Couple-Ice-and-Water-Dynamics","page":"Halfar-NS","title":"Couple Ice and Water Dynamics","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's use Decapodes to implement the incompressible Navier-Stokes as given by Mohamed et al.. We will run these dynamics on the sphere. We will couple this model with Halfar glacier dynamics on the sphere. For the initial conditions of the Halfar ice thickness, we will use an idealized polar ice cap.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Note that the time scale at which ice creeps is much larger than the time scale at which the water in the ocean would flow. So we can either choose to model a very slow moving fluid around the ice (like a storm on a gas giant), or we can choose to model on a shorter timescale, on which the ice does not move very much.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nPoint3D = Point3{Float64};\nnothing # hide","category":"page"},{"location":"halmo/halmo/#Specify-our-models","page":"Halfar-NS","title":"Specify our models","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Our first component is the Mohamed et al. formulation of the incompressible Navier-Stokes equations. We will call the flow here \"w\". This will be the flow after collisions with glaciers are considered.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"This is Equation 10 for N=2.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"eq10forN2 = @decapode begin\n (𝐮,w)::DualForm1\n (P, 𝑝ᵈ)::DualForm0\n μ::Constant\n\n 𝑝ᵈ == P + 0.5 * ι₁₁(w,w)\n\n ∂ₜ(𝐮) == μ * ∘(d, ⋆, d, ⋆)(w) + (-1)*⋆₁⁻¹(∧ᵈᵖ₁₀(w, ⋆(d(w)))) + d(𝑝ᵈ)\nend\nto_graphviz(eq10forN2)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Halfar's equation and Glen's law are composed like so:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ∂ₜ(h) == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nglens_law = @decapode begin\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\nice_dynamics = apex(oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])]))\n\nto_graphviz(ice_dynamics, verbose=false)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We now have our dynamics that govern glaciers, and our dynamics that govern water. We need to specify the physics of what happens when glaciers and water interact. There are many options, and the choice you make depends on the time-scale and resolution of the dynamics that you are interested in.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"An interaction between glacier and water dynamics can look like the following, where flow_after is the flow of water after interaction with ice is considered.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"ice_water_composition_diagram = @relation () begin\n glacier_dynamics(ice_thickness)\n water_dynamics(flow, flow_after)\n\n interaction(ice_thickness, flow, flow_after)\nend\ndraw_composition(ice_water_composition_diagram)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We will use the language of Decapodes to encode the dynamics that ice blocks water from flowing.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can detect the ice with a sigmoid function. Where there is ice, we want the flow to be 0, and where there is no ice, we will not impede the flow. We won't consider any further special boundary conditions between ice and water here. Since h is a scalar-like quantity, and flow is a vector-like quantity, we can relate them using the wedge product operator from the exterior calculus. We can state these dynamics using the language of Decapodes like so:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"blocking = @decapode begin\n h::Form0\n (𝐮,w)::DualForm1\n\n w == (1-σ(h)) ∧ᵖᵈ₀₁ 𝐮\nend\nto_graphviz(blocking)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Here, σ is a sigmoid function that is 0 when d(h) is 0, and goes to 1 otherwise. We see that w is indeed defined as 𝐮, after interacting with the ice boundary is considered.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can apply our composition diagram to generate our physics:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"ice_water = apex(oapply(ice_water_composition_diagram,\n [Open(ice_dynamics, [:dynamics_h]),\n Open(eq10forN2, [:𝐮, :w]),\n Open(blocking, [:h, :𝐮, :w])]))\n\nto_graphviz(ice_dynamics, verbose=false)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can now generate our simulation:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"sim = eval(gensim(ice_water))","category":"page"},{"location":"halmo/halmo/#Meshes-and-Initial-Conditions","page":"Halfar-NS","title":"Meshes and Initial Conditions","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Since we want to demonstrate these physics on the Earth, we will use one of our icosphere discretizations with the appropriate radius.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"rₑ = 6378e3 # [km]\ns = loadmesh(Icosphere(5, rₑ))\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nwireframe(sd)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's demonstrate how to add operators by providing the definition of a sigmoid function:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"sigmoid(x) = (2 ./ (1 .+ exp.(-x*1e2)) .- 1)\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n # This is a new function.\n :σ => sigmoid\n :mag => x -> norm.(x)\n # Remaining operations (such as our differential operators) are built-in.\n _ => default_dec_matrix_generate(sd, my_symbol, hodge)\n end\n return op\nend;","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's combine our mesh with our physics to instantiate our simulation:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"fₘ = sim(sd, generate);","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can now supply initial conditions:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"ice_thickness = map(sd[:point]) do (_,_,z)\n z < 0.8*rₑ ? 0 : 1\nend\n\nflow = dec_dual_derivative(0,sd) *\n map(sd[sd[:tri_center], :dual_point]) do (_,_,z)\n (rₑ-abs(z))/rₑ\n end\n\n# There is no water \"under\" the ice:\nflow = dec_wedge_product_pd(Tuple{0,1},sd)(1 .- sigmoid(ice_thickness), flow)\n\nu₀ = ComponentArray(\n ice_thickness = ice_thickness,\n flow = flow,\n water_dynamics_P = zeros(ntriangles(sd)))\n\nconstants_and_parameters = (\n glacier_dynamics_n = 3,\n glacier_dynamics_stress_A = fill(1e-16, ne(sd)),\n glacier_dynamics_stress_ρ = 910,\n glacier_dynamics_stress_g = 9.8101,\n water_dynamics_μ = 0.01);","category":"page"},{"location":"halmo/halmo/#Execute-the-Simulation","page":"Halfar-NS","title":"Execute the Simulation","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We specified our physics, our mesh, and our initial conditions. We have everything we need to execute the simulation.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"tₑ = 100\n\n# Julia will pre-compile the generated simulation the first time it is run.\n@info(\"Precompiling Solver\")\nprob = ODEProblem(fₘ, u₀, (0, 1e-4), constants_and_parameters)\nsoln = solve(prob, Vern7())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Vern7())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"halmo/halmo/#Results","page":"Halfar-NS","title":"Results","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's look at the dynamics of the ice:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"begin\n frames = 200\n fig = Figure()\n ax = LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.mesh!(ax, s, color=soln(0).ice_thickness, colormap=:jet, colorrange=extrema(soln(0).ice_thickness))\n\n Colorbar(fig[1,2], msh)\n record(fig, \"halmo_ice.gif\", range(0.0, tₑ; length=frames); framerate = 20) do t\n msh.color = soln(t).ice_thickness\n end\nend","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"(Image: HalfarMohamedIce)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"ascii/#ASCII-and-Vector-Calculus-Operators","page":"ASCII Operators","title":"ASCII and Vector Calculus Operators","text":"","category":"section"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"include(joinpath(Base.@__DIR__, \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"Some users may have trouble entering unicode characters like ⋆ or ∂ in their development environment. So, we offer the following ASCII equivalents. Further, some users may like to use vector calculus symbols instead of exterior calculus symbols where possible. We offer support for such symbols as well.","category":"page"},{"location":"ascii/#ASCII-Equivalents","page":"ASCII Operators","title":"ASCII Equivalents","text":"","category":"section"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"Unicode ASCII Meaning\n∂ₜ dt derivative w.r.t. time\n⋆ star Hodge star, generalizing transpose\nΔ lapl laplacian\n∧ wedge wedge product, generalizing the cross product\n⋆⁻¹ star_inv Hodge star, generalizing transpose\n∘(⋆,d,⋆) div divergence, a.k.a. ∇⋅\navg₀₁ avg_01 average values stored on endpoints of edges","category":"page"},{"location":"ascii/#Vector-Calculus-Equivalents","page":"ASCII Operators","title":"Vector Calculus Equivalents","text":"","category":"section"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"Vec DEC How to enter\ngrad d grad\ndiv ∘(⋆,d,⋆) div\ncurl ∘(d,⋆) curl\n∇ d \\nabla\n∇ᵈ ∘(⋆,d,⋆) \\nabla \\ \\^d \\\n∇x ∘(d,⋆) \\nabla \\ x\nadv(X,Y) ∘(⋆,d,⋆)(X∧Y) adv","category":"page"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"#Decapodes.jl","page":"Decapodes.jl","title":"Decapodes.jl","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"Decapodes.jl is a framework for developing, composing, and simulating physical systems.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"Decapodes.jl is the synthesis of Applied Category Theory (ACT) techniques for formalizing and composing physics equations, and Discrete Exterior Calculus (DEC) techniques for formalizing differential operators. CombinatorialSpaces.jl hosts tools for discretizing space and defining DEC operators on simplicial complexes, and DiagrammaticEquations.jl hosts tooling for representing the equations as formal ACT diagrams. This repository combines these two packages, compiling diagrams down to runnable simulation code.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"By combining the power of ACT and the DEC, we seek to improve the scientific computing workflow. Decapodes simulations are hierarchically composable, generalize over any type of manifold, and are performant and accurate with a declarative domain specific language (DSL) that is human-readable.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"(Image: Grigoriev Ice Cap Dynamics)","category":"page"},{"location":"#Getting-started","page":"Decapodes.jl","title":"Getting started","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"Walkthroughs creating, composing, and solving Decapodes are available in the side-bar of this documentation page. Further example scripts are available in the examples folder of the Decapodes.jl GitHub repo, and will be added to this documentation site soon.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"note: Under Active Development\nThis library is currently under active development, and so is not yet at a point where a constant API/behavior can be assumed. That being said, if this project looks interesting/relevant please contact us and let us know!","category":"page"}] +[{"location":"bc/bc_debug/#Simulation-Setup","page":"Misc Features","title":"Simulation Setup","text":"","category":"section"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"This tutorial showcases some of the other features included in the Decapodes.jl package. Currently, these features are the treatment of boundary conditions and the simulation debugger interface. To begin, we set up the same advection-diffusion problem presented in the Overview section. As before, we define the Diffusion, Advection, and Superposition components, and now include a Boundary Condition (BC) component. By convention, BCs are encoded in Decapodes by using a ∂ symbol. Below we show the graphical rendering of this boundary condition diagram, which we will use to impose a Dirichlet condition on the time derivative of concentration at the mesh boundary.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using Catlab\nusing DiagrammaticEquations\nusing Decapodes\n\nDiffusion = @decapode begin\n C::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\nend\n\nAdvection = @decapode begin\n C::Form0\n ϕ::Form1\n V::Constant\n\n ϕ == ∧₀₁(C,V)\nend\n\nSuperposition = @decapode begin\n (C, C_up)::Form0\n (ϕ, ϕ₁, ϕ₂)::Form1\n\n ϕ == ϕ₁ + ϕ₂\n C_up == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\nend\n\nBoundaryConditions = @decapode begin\n (C, C_up)::Form0\n\n # Temporal boundary\n ∂ₜ(C) == Ċ\n\n # Spatial boundary\n Ċ == ∂C(C_up)\nend\n\nto_graphviz(BoundaryConditions)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"As before, we compose these physics components over our wiring diagram.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"compose_diff_adv = @relation (C, V) begin\n diffusion(C, ϕ₁)\n advection(C, ϕ₂, V)\n bc(C, C_up)\n superposition(ϕ₁, ϕ₂, ϕ, C_up, C)\nend\n\ndraw_composition(compose_diff_adv)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"DiffusionAdvection_cospan = oapply(compose_diff_adv,\n [Open(Diffusion, [:C, :ϕ]),\n Open(Advection, [:C, :ϕ, :V]),\n Open(BoundaryConditions, [:C, :C_up]),\n Open(Superposition, [:ϕ₁, :ϕ₂, :ϕ, :C_up, :C])])\nDiffusionAdvection = apex(DiffusionAdvection_cospan)\n\nto_graphviz(DiffusionAdvection)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"When this is scheduled, Decapodes will apply any boundary conditions immediately after the impacted value is computed. This implementation choice ensures that this boundary condition holds true for any variables dependent on this variable, though also means that the boundary conditions on a variable have no immediate impact on the variables this variable is dependent on.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"In the visualization below, we see that the final operation executed on the data is the boundary condition we are enforcing on the change in concentration.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"to_graphviz(DiffusionAdvection)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"Next we import the mesh we will use. In this case, we are wanting to impose boundary conditions and so we will use the plot_mesh from the previous example instead of the mesh with periodic boundary conditions. Because the mesh is only a primal mesh, we also generate and subdivide the dual mesh.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using CombinatorialSpaces\nusing CairoMakie\n\nplot_mesh = loadmesh(Rectangle_30x10())\n\n# Generate the dual mesh\nplot_mesh_dual = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3{Float64}}(plot_mesh)\n\n# Calculate distances and subdivisions for the dual mesh\nsubdivide_duals!(plot_mesh_dual, Circumcenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nwireframe!(ax, plot_mesh)\nfig","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"Finally, we define our operators, generate the simulation function, and compute the simulation. Note that when we define the boundary condition operator, we hardcode the boundary indices and values into the operator itself. We also move the initial concentration to the left, so that we are able to see a constant concentration on the left boundary which will act as a source in the flow. You can find the file for boundary conditions here. The modified initial condition is shown below:","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using LinearAlgebra\nusing ComponentArrays\nusing MLStyle\ninclude(\"../boundary_helpers.jl\")\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :k => x -> 0.05*x\n :∂C => x -> begin\n boundary = boundary_inds(Val{0}, sd)\n x[boundary] .= 0\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend\n\nusing Distributions\nc_dist = MvNormal([1, 5], [1.5, 1.5])\nc = [pdf(c_dist, [p[1], p[2]]) for p in plot_mesh_dual[:point]]\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nmesh!(ax, plot_mesh; color=c[1:nv(plot_mesh)])\nfig","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"And the simulation result is then computed and visualized below.","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"using OrdinaryDiffEq\n\nsim = eval(gensim(DiffusionAdvection))\nfₘ = sim(plot_mesh_dual, generate)\n\nvelocity(p) = [-0.5, 0.0, 0.0]\nv = ♭(plot_mesh_dual, DualVectorField(velocity.(plot_mesh_dual[triangle_center(plot_mesh_dual),:dual_point]))).data\n\nu₀ = ComponentArray(C=c)\nparams = (V = v,)\n\nprob = ODEProblem(fₘ, u₀, (0.0, 100.0), params)\nsol = solve(prob, Tsit5());\n\n# Plot the result\ntimes = range(0.0, 100.0, length=150)\ncolors = [sol(t).C for t in times]\nextrema\n# Initial frame\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\npmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))\nColorbar(fig[1,2], pmsh)\nframerate = 30\n\n# Animation\nrecord(fig, \"diff_adv_right.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n pmsh.color = sol(t).C\nend","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"(Image: Diffusion-Advection result and your first BC Decapode!)","category":"page"},{"location":"bc/bc_debug/","page":"Misc Features","title":"Misc Features","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"navier_stokes/ns/#Navier-Stokes-Vorticity-Model","page":"Vortices","title":"Navier Stokes Vorticity Model","text":"","category":"section"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"This is a discretization of the incompressible Navier Stokes equations using the Discrete Exterior Calculus.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"The formulations are based on those given by Mohamed, Hirani, Samtaney (in turn from Marsden, Ratiu, Abraham).","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"However, different choices in discretization are chosen for purposes of brevity, to demonstrate novel discretizations of certain operators, and to demonstrate the automated Decapodes workflow.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"The different formulations are given in the following decapode expressions. The full code that generated these results is available in a julia script.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"eq11_vorticity = @decapode begin\n d𝐮::DualForm2\n 𝐮::DualForm1\n μ::Constant\n\n 𝐮 == d₁⁻¹(d𝐮)\n\n ∂ₜ(d𝐮) == μ * ∘(⋆, d, ⋆, d)(d𝐮) + (-1) * ∘(♭♯, ⋆₁, d̃₁)(∧ᵈᵖ₁₀(𝐮, ⋆(d𝐮)))\nend\n\neq11_inviscid_vorticity = @decapode begin\n d𝐮::DualForm2\n 𝐮::DualForm1\n\n 𝐮 == d₁⁻¹(d𝐮)\n\n ∂ₜ(d𝐮) == (-1) * ∘(♭♯, ⋆₁, d̃₁)(∧ᵈᵖ₁₀(𝐮, ⋆(d𝐮)))\nend\n\neq11_inviscid_poisson = @decapode begin\n d𝐮::DualForm2\n 𝐮::DualForm1\n ψ::Form0\n\n ψ == Δ⁻¹(⋆(d𝐮))\n 𝐮 == ⋆(d(ψ))\n\n ∂ₜ(d𝐮) == (-1) * ∘(♭♯, ⋆₁, d̃₁)(∧ᵈᵖ₁₀(𝐮, ⋆(d𝐮)))\nend\n\neq17_stream = @decapode begin\n ψ::Form0\n u::DualForm1\n v::Form1\n μ::Constant\n\n u == ⋆(d(ψ))\n v == ⋆(u)\n\n ∂ₜ(ψ) == dsdinv(\n μ * ∘(d, ⋆, d, ⋆, d, ⋆, d)(ψ) -\n ∘(⋆₁, d̃₁)(v ∧ ∘(d,⋆,d,⋆)(ψ)))\nend\n\neq17_inviscid_stream = @decapode begin\n ψ::Form0\n u::DualForm1\n v::Form1\n\n u == ⋆(d(ψ))\n v == ⋆(u)\n\n ∂ₜ(ψ) == -1 * dsdinv(∘(⋆₁, d̃₁)(v ∧ ∘(d,⋆,d,⋆)(ψ)))\nend","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"Our initial conditions of interest are either Taylor or Point vortices","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"function taylor_vortex(pnt::Point3D, cntr::Point3D, p::TaylorVortexParams)\n gcd = great_circle_dist(pnt,cntr)\n (p.G/p.a) * (2 - (gcd/p.a)^2) * exp(0.5 * (1 - (gcd/p.a)^2))\nend\n\nfunction point_vortex(pnt::Point3D, cntr::Point3D, p::PointVortexParams)\n gcd = great_circle_dist(pnt,cntr)\n p.τ / (cosh(3gcd/p.a)^2)\nend","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"Based on the configuration, you can see different results that match the expected solutions from the literature.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"Here is one set of results from using the inviscid Poisson formulation:","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"(Image: Vorticity)","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"These vortices should be stable so we should see the same periodic function for both lines here. The difference between the lines is the accumulated error.","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"(Image: Azimuth Profile)","category":"page"},{"location":"navier_stokes/ns/","page":"Vortices","title":"Vortices","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"equations/equations/#Simple-Equations","page":"Equations","title":"Simple Equations","text":"","category":"section"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\",\"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"This tutorial shows how to use Decapodes to represent simple equations. These aren't using any of the Discrete Exterior Calculus or CombinatorialSpaces features of Decapodes. They just are a reference for how to build equations with the @decapodes macro and see how they are stored as ACSets.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"using Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"The harmonic oscillator can be written in Decapodes in at least three different ways.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -k(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"The default representation is a tabular output as an ACSet. The tables are Var for storing variables (X) and their types (Form0). TVar for identifying a subset of variables that are the tangent variables of the dynamics (Ẋ). The unary operators are stored in Op1 and binary operators stored in Op2. If a table is empty, it doesn't get printed.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"Even though a diagrammatic equation is like a graph, there are no edge tables, because the arity (number of inputs) and coarity (number of outputs) is baked into the operator definitions.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"You can also see the output as a directed graph. The input arrows point to the state variables of the system and the output variables point from the tangent variables. You can see that I have done the differential degree reduction from x'' = -kx by introducing a velocity term v. Decapodes has some support for derivatives in the visualization layer, so it knows that dX/dt should be called Ẋ and that dẊ/dt should be called Ẋ̇.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"In the previous example, we viewed negation and transformation by k as operators. Notice that k appears as an edge in the graph and not as a vertex. You can also use a 2 argument function like multiplication (*). With a constant value for k::Constant. In this case you will see k enter the diagram as a vertex and multiplication with * as a binary operator.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n k::Constant\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -k*(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"This gives you a different graphical representation as well. Now we have the cartesian product objects which represent a tupling of two values.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"You can also represent negation as a multiplication by a literal -1.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n k::Constant\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -1*k*(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"Notice that the type bubble for the literal one is ΩL. This means that it is a literal. The literal is also used as the variable name.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"We can allow the material properties to vary over time by changing Constant to Parameter. This is how we tell the simulator that it needs to call k(t) at each time step to get the updated value for k or if it can just reuse that constant k from the initial time step.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"oscillator = @decapode begin\n X::Form0\n V::Form0\n\n k::Parameter\n\n ∂ₜ(X) == V\n ∂ₜ(V) == -1*k*(X)\nend","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"Often you will have a linear material where you are scaling by a constant, and a nonlinear version of that material where that scaling is replaced by a generic nonlinear function. This is why we allow Decapodes to represent both of these types of equations.","category":"page"},{"location":"equations/equations/","page":"Equations","title":"Equations","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"canon/#Canon","page":"Canonical Models","title":"Canon","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"include(joinpath(Base.@__DIR__, \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"canon/#Physics","page":"Canonical Models","title":"Physics","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Physics ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Physics.:heat_transfer","page":"Canonical Models","title":"Decapodes.Canon.Physics.:heat_transfer","text":"Heat Transfer\n\nSource\n\nModel\n\n(HT, Tₛ)::Form0\n \n(D, cosϕᵖ, cosϕᵈ)::Constant\n \nHT == (D ./ cosϕᵖ) .* (⋆)(d(cosϕᵈ .* (⋆)(d(Tₛ))))\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.:outgoing_longwave_radiation","page":"Canonical Models","title":"Decapodes.Canon.Physics.:outgoing_longwave_radiation","text":"Outgoing Longwave Radiation\n\nSource\n\nModel\n\n(Tₛ, OLR)::Form0\n \n(A, B)::Constant\n \nOLR == A .+ B .* Tₛ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.absorbed_shortwave_radiation","page":"Canonical Models","title":"Decapodes.Canon.Physics.absorbed_shortwave_radiation","text":"Absorbed Shortwave Radiation\n\nSource\n\nThe proportion of light reflected by a surface is the albedo. The absorbed shortwave radiation is the complement of this quantity.\n\nModel\n\n(Q, ASR)::Form0\n \nα::Constant\n \nASR == (1 .- α) .* Q\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.advection","page":"Canonical Models","title":"Decapodes.Canon.Physics.advection","text":"Advection\n\nSource\n\nAdvection refers to the transport of a bulk along a vector field.\n\nModel\n\nC::Form0\n \n(ϕ, V)::Form1\n \nϕ == C ∧₀₁ V\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.ficks_law","page":"Canonical Models","title":"Decapodes.Canon.Physics.ficks_law","text":"Ficks Law\n\nSource\n\nEquation for diffusion first stated by Adolf Fick. The diffusion flux is proportional to the concentration gradient.\n\nModel\n\nC::Form0\n \nϕ::Form1\n \nϕ == k(d₀(C))\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.iceblockingwater","page":"Canonical Models","title":"Decapodes.Canon.Physics.iceblockingwater","text":"IceBlockingWater\n\nSource\n\nModel\n\nh::Form0\n \n(𝐮, w)::DualForm1\n \nw == (1 - σ(h)) ∧ᵖᵈ₀₁ 𝐮\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.jko_scheme","page":"Canonical Models","title":"Decapodes.Canon.Physics.jko_scheme","text":"Jordan-Kinderlehrer-Otto\n\nSource\n\nJordan, R., Kinderlehrer, D., & Otto, F. (1998). The Variational Formulation of the Fokker–Planck Equation. In SIAM Journal on Mathematical Analysis (Vol. 29, Issue 1, pp. 1–17). Society for Industrial & Applied Mathematics (SIAM). https://doi.org/10.1137/s0036141096303359\n\nModel\n\n(ρ, Ψ)::Form0\n \nβ⁻¹::Constant\n \n∂ₜ(ρ) == (∘(⋆, d, ⋆))(d(Ψ) ∧ ρ) + β⁻¹ * Δ(ρ)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.lie","page":"Canonical Models","title":"Decapodes.Canon.Physics.lie","text":"Lie\n\nSource\n\nModel\n\nC::Form0\n \nV::Form1\n \ndX::Form1\n \nV == ((⋆) ∘ (⋆))(C ∧ dX)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.mohamed_flow","page":"Canonical Models","title":"Decapodes.Canon.Physics.mohamed_flow","text":"Mohamed Eq. 10, N2\n\nSource\n\nModel\n\n(𝐮, w)::DualForm1\n \n(P, 𝑝ᵈ)::DualForm0\n \nμ::Constant\n \n𝑝ᵈ == P + 0.5 * ι₁₁(w, w)\n \n∂ₜ(𝐮) == μ * (∘(d, ⋆, d, ⋆))(w) + -1 * (⋆₁⁻¹)(w ∧ᵈᵖ₁₀ (⋆)(d(w))) + d(𝑝ᵈ)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.momentum","page":"Canonical Models","title":"Decapodes.Canon.Physics.momentum","text":"Momentum\n\nSource\n\nModel\n\n(f, b)::Form0\n \n(v, V, g, Fᵥ, uˢ, v_up)::Form1\n \nτ::Form2\n \nU::Parameter\n \nuˢ̇ == ∂ₜ(uˢ)\n \nv_up == (((((((-1 * L(v, v) - L(V, v)) - L(v, V)) - f ∧ v) - (∘(⋆, d, ⋆))(uˢ) ∧ v) - d(p)) + b ∧ g) - (∘(⋆, d, ⋆))(τ)) + uˢ̇ + Fᵥ\n \nuˢ̇ == force(U)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.navier_stokes","page":"Canonical Models","title":"Decapodes.Canon.Physics.navier_stokes","text":"Navier-Stokes\n\nSource\n\nPartial differential equations which describe the motion of viscous fluid surfaces.\n\nModel\n\n(V, V̇, G)::Form1{X}\n \n(ρ, ṗ, p)::Form0{X}\n \nV̇ == neg₁(L₁′(V, V)) + div₁(kᵥ(Δ₁(V) + third(d₀(δ₁(V)))), avg₀₁(ρ)) + d₀(half(i₁′(V, V))) + neg₁(div₁(d₀(p), avg₀₁(ρ))) + G\n \n∂ₜ(V) == V̇\n \nṗ == neg₀((⋆₀⁻¹)(L₀(V, (⋆₀)(p))))\n \n∂ₜ(p) == ṗ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.oscillator","page":"Canonical Models","title":"Decapodes.Canon.Physics.oscillator","text":"Oscillator\n\nSource\n\nEquation governing the motion of an object whose acceleration is negatively-proportional to its position.\n\nModel\n\nX::Form0\n \nV::Form0\n \nk::Constant\n \n∂ₜ(X) == V\n \n∂ₜ(V) == -k * X\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.poiseuille","page":"Canonical Models","title":"Decapodes.Canon.Physics.poiseuille","text":"Poiseuille\n\nSource\n\nA relation between the pressure drop in an incompressible and Newtownian fluid in laminar flow flowing through a long cylindrical pipe.\n\nModel\n\nP::Form0\n \nq::Form1\n \n(R, μ̃)::Constant\n \nΔq == Δ(q)\n \n∇P == d(P)\n \n∂ₜ(q) == q̇\n \nq̇ == μ̃ * ∂q(Δq) + ∇P + R * q\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.poiseuille_density","page":"Canonical Models","title":"Decapodes.Canon.Physics.poiseuille_density","text":"Poiseuille Density\n\nSource\n\nModel\n\nq::Form1\n \n(P, ρ)::Form0\n \n(k, R, μ̃)::Constant\n \n∂ₜ(q) == q̇\n \n∇P == d(P)\n \nq̇ == (μ̃ * ∂q(Δ(q)) - ∇P) + R * q\n \nP == k * ρ\n \n∂ₜ(ρ) == ρ̇\n \nρ_up == (∘(⋆, d, ⋆))(-1 * (ρ ∧₀₁ q))\n \nρ̇ == ∂ρ(ρ_up)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.schroedinger","page":"Canonical Models","title":"Decapodes.Canon.Physics.schroedinger","text":"Schroedinger\n\nSource\n\nThe evolution of the wave function over time.\n\nModel\n\n(i, h, m)::Constant\n \nV::Parameter\n \nΨ::Form0\n \n∂ₜ(Ψ) == (((-1 * h ^ 2) / (2m)) * Δ(Ψ) + V * Ψ) / (i * h)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Physics.superposition","page":"Canonical Models","title":"Decapodes.Canon.Physics.superposition","text":"Superposition\n\nSource\n\nModel\n\n(C, Ċ)::Form0\n \n(ϕ, ϕ₁, ϕ₂)::Form1\n \nϕ == ϕ₁ + ϕ₂\n \nĊ == (⋆₀⁻¹)(dual_d₁((⋆₁)(ϕ)))\n \n∂ₜ(C) == Ċ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Chemistry","page":"Canonical Models","title":"Chemistry","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Chemistry ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Chemistry.GrayScott","page":"Canonical Models","title":"Decapodes.Canon.Chemistry.GrayScott","text":"Gray-Scott\n\nSource\n\nA model of reaction-diffusion\n\nModel\n\n(U, V)::Form0\n \nUV2::Form0\n \n(U̇, V̇)::Form0\n \n(f, k, rᵤ, rᵥ)::Constant\n \nUV2 == U .* (V .* V)\n \nU̇ == (rᵤ * Δ(U) - UV2) + f * (1 .- U)\n \nV̇ == (rᵥ * Δ(V) + UV2) - (f + k) .* V\n \n∂ₜ(U) == U̇\n \n∂ₜ(V) == V̇\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Chemistry.brusselator","page":"Canonical Models","title":"Decapodes.Canon.Chemistry.brusselator","text":"Brusselator\n\nSource\n\nA model of reaction-diffusion for an oscillatory chemical system.\n\nModel\n\n(U, V)::Form0\n \nU2V::Form0\n \n(U̇, V̇)::Form0\n \nα::Constant\n \nF::Parameter\n \nU2V == (U .* U) .* V\n \nU̇ == ((1 + U2V) - 4.4U) + α * Δ(U) + F\n \nV̇ == (3.4U - U2V) + α * Δ(V)\n \n∂ₜ(U) == U̇\n \n∂ₜ(V) == V̇\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Biology","page":"Canonical Models","title":"Biology","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Biology ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Biology.kealy","page":"Canonical Models","title":"Decapodes.Canon.Biology.kealy","text":"Kealy\n\nSource\n\nModel\n\n(n, w)::DualForm0\n \ndX::Form1\n \n(a, ν)::Constant\n \n∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * Δ(w)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.klausmeier_2a","page":"Canonical Models","title":"Decapodes.Canon.Biology.klausmeier_2a","text":"Klausmeier (Eq. 2a)\n\nSource\n\nKlausmeier, CA. “Regular and irregular patterns in semiarid vegetation.” Science (New York, N.Y.) vol. 284,5421 (1999): 1826-8. doi:10.1126/science.284.5421.1826\n\nModel\n\n(n, w)::DualForm0\n \ndX::Form1\n \n(a, ν)::Constant\n \n∂ₜ(w) == ((a - w) - w * n ^ 2) + ν * ℒ(dX, w)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.klausmeier_2b","page":"Canonical Models","title":"Decapodes.Canon.Biology.klausmeier_2b","text":"Klausmeier (Eq. 2b)\n\nSource\n\nibid.\n\nModel\n\n(n, w)::DualForm0\n \nm::Constant\n \n∂ₜ(n) == (w * n ^ 2 - m * n) + Δ(n)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.lejeune","page":"Canonical Models","title":"Decapodes.Canon.Biology.lejeune","text":"Lejeune\n\nSource\n\nLejeune, O., & Tlidi, M. (1999). A Model for the Explanation of Vegetation Stripes (Tiger Bush). Journal of Vegetation Science, 10(2), 201–208. https://doi.org/10.2307/3237141\n\nModel\n\nρ::Form0\n \n(μ, Λ, L)::Constant\n \n∂ₜ(ρ) == (ρ * (((1 - μ) + (Λ - 1) * ρ) - ρ * ρ) + 0.5 * (L * L - ρ) * Δ(ρ)) - 0.125 * ρ * Δ(ρ) * Δ(ρ)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Biology.turing_continuous_ring","page":"Canonical Models","title":"Decapodes.Canon.Biology.turing_continuous_ring","text":"Turing Continuous Ring\n\nSource\n\nModel\n\n(X, Y)::Form0\n \n(μ, ν, a, b, c, d)::Constant\n \n∂ₜ(X) == a * X + b * Y + μ * Δ(X)\n \n∂ₜ(Y) == c * X + d * Y + ν * Δ(X)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Environment","page":"Canonical Models","title":"Environment","text":"","category":"section"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"Modules = [ Decapodes.Canon.Environment ]\nPrivate = false","category":"page"},{"location":"canon/#Decapodes.Canon.Environment.boundary_conditions","page":"Canonical Models","title":"Decapodes.Canon.Environment.boundary_conditions","text":"Boundary Conditions\n\nSource\n\nModel\n\n(S, T)::Form0\n \n(Ṡ, T_up)::Form0\n \nv::Form1\n \nv_up::Form1\n \nṪ == ∂ₜ(T)\n \nṠ == ∂ₜ(S)\n \nv̇ == ∂ₜ(v)\n \nṪ == ∂_spatial(T_up)\n \nv̇ == ∂_noslip(v_up)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.energy_balance","page":"Canonical Models","title":"Decapodes.Canon.Environment.energy_balance","text":"Energy balance\n\nSource\n\nenergy balance equation from Budyko Sellers\n\nModel\n\n(Tₛ, ASR, OLR, HT)::Form0\n \nC::Constant\n \nTₛ̇ == ∂ₜ(Tₛ)\n \nTₛ̇ == ((ASR - OLR) + HT) ./ C\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.equation_of_state","page":"Canonical Models","title":"Decapodes.Canon.Environment.equation_of_state","text":"Equation of State\n\nSource\n\nModel\n\n(b, T, S)::Form0\n \n(g, α, β)::Constant\n \nb == g * (α * T - β * S)\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.glen","page":"Canonical Models","title":"Decapodes.Canon.Environment.glen","text":"Glens Law\n\nSource\n\nNye, J. F. (1957). The Distribution of Stress and Velocity in Glaciers and Ice-Sheets. Proceedings of the Royal Society of London. Series A, Mathematical and Physical Sciences, 239(1216), 113–133. http://www.jstor.org/stable/100184\n\nModel\n\nΓ::Form1\n \n(A, ρ, g, n)::Constant\n \nΓ == (2 / (n + 2)) * A * (ρ * g) ^ n\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.halfar_eq2","page":"Canonical Models","title":"Decapodes.Canon.Environment.halfar_eq2","text":"Halfar (Eq. 2)\n\nSource\n\nHalfar, P. (1981), On the dynamics of the ice sheets, J. Geophys. Res., 86(C11), 11065–11072, doi:10.1029/JC086iC11p11065\n\nModel\n\nh::Form0\n \nΓ::Form1\n \nn::Constant\n \n∂ₜ(h) == (∘(⋆, d, ⋆))(((Γ * d(h)) ∧ mag(♯(d(h))) ^ (n - 1)) ∧ h ^ (n + 2))\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.insolation","page":"Canonical Models","title":"Decapodes.Canon.Environment.insolation","text":"Insolation\n\nSource\n\nModel\n\nQ::Form0\n \ncosϕᵖ::Constant\n \nQ == 450cosϕᵖ\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.tracer","page":"Canonical Models","title":"Decapodes.Canon.Environment.tracer","text":"Tracer\n\nSource\n\nModel\n\n(c, C, F, c_up)::Form0\n \n(v, V, q)::Form1\n \nc_up == (((-1 * (⋆)(L(v, (⋆)(c))) - (⋆)(L(V, (⋆)(c)))) - (⋆)(L(v, (⋆)(C)))) - (∘(⋆, d, ⋆))(q)) + F\n\n\n\n\n\n","category":"constant"},{"location":"canon/#Decapodes.Canon.Environment.warming","page":"Canonical Models","title":"Decapodes.Canon.Environment.warming","text":"Warming\n\nSource\n\nModel\n\nTₛ::Form0\n \nA::Form1\n \nA == avg₀₁(5.8282 * 10 ^ (-0.236Tₛ) * 1.65e7)\n\n\n\n\n\n","category":"constant"},{"location":"canon/","page":"Canonical Models","title":"Canonical Models","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"api/#Library-Reference","page":"Library Reference","title":"Library Reference","text":"","category":"section"},{"location":"api/","page":"Library Reference","title":"Library Reference","text":"include(joinpath(Base.@__DIR__, \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"api/#Decapodes","page":"Library Reference","title":"Decapodes","text":"","category":"section"},{"location":"api/","page":"Library Reference","title":"Library Reference","text":"Modules = [ Decapodes ]\nPrivate = false","category":"page"},{"location":"api/#Decapodes.compile-Tuple{DiagrammaticEquations.decapodeacset.SummationDecapode, Vector{Symbol}, Vector{Decapodes.AllocVecCall}, Set{Symbol}, Int64, DataType, Decapodes.AbstractGenerationTarget}","page":"Library Reference","title":"Decapodes.compile","text":"compile(d::SummationDecapode, inputs::Vector{Symbol}, alloc_vectors::Vector{AllocVecCall}, optimizable_dec_operators::Set{Symbol}, dimension::Int, stateeltype::DataType, code_target::GenerationTarget)\n\nFunction that compiles the computation body. d is the input Decapode, inputs is a vector of state variables and literals, alloc_vec should be empty when passed in, optimizable_dec_operators is a collection of all DEC operator symbols that can use special in-place methods, dimension is the dimension of the problem (usually 1 or 2), stateeltype is the type of the state elements (usually Float32 or Float64) and code_target determines what architecture the code is compiled for (either CPU or CUDA).\n\n\n\n\n\n","category":"method"},{"location":"api/#Decapodes.compile_env-Tuple{DiagrammaticEquations.decapodeacset.SummationDecapode, Vector{Symbol}, Set{Symbol}, Decapodes.AbstractGenerationTarget}","page":"Library Reference","title":"Decapodes.compile_env","text":"compile_env(d::SummationDecapode, dec_matrices::Vector{Symbol}, con_dec_operators::Set{Symbol}, code_target::GenerationTarget)\n\nThis creates the symbol to function linking for the simulation output. Those run through the default_dec backend expect both an in-place and an out-of-place variant in that order. User defined operations only support out-of-place.\n\n\n\n\n\n","category":"method"},{"location":"api/#Decapodes.gensim-Tuple{DiagrammaticEquations.decapodeacset.SummationDecapode, Vector{Symbol}}","page":"Library Reference","title":"Decapodes.gensim","text":"gensim(user_d::SummationDecapode, input_vars::Vector{Symbol}; dimension::Int=2, stateeltype::DataType = Float64, code_target::GenerationTarget = CPUTarget())\n\nGenerates the entire code body for the simulation function. user_d is the user passed Decapodes which will be left unmodified and 'input_vars' is the collection of state variables and literals in the Decapode.\n\nOptional keyword arguments are dimension, which is the dimension of the problem and defaults to 2D, stateeltype, which is the element type of the state forms and defaults to Float64 and code_target, which is the intended architecture target for the generated code, defaulting to regular CPU compatible code.\n\n\n\n\n\n","category":"method"},{"location":"api/#Decapodes.gensim-Tuple{DiagrammaticEquations.decapodeacset.SummationDecapode}","page":"Library Reference","title":"Decapodes.gensim","text":"function gensim(d::SummationDecapode; dimension::Int=2)\n\nGenerate a simulation function from the given Decapode. The returned function can then be combined with a mesh and a function describing function mappings to return a simulator to be passed to solve.\n\n\n\n\n\n","category":"method"},{"location":"api/","page":"Library Reference","title":"Library Reference","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"cism/cism/#Replicating-the-Community-Ice-Sheet-Model-v2.1-Halfar-Dome-Benchmark-with-Decapodes","page":"CISM v2.1","title":"Replicating the Community Ice Sheet Model v2.1 Halfar Dome Benchmark with Decapodes","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"The Decapodes framework takes high-level representations of physics equations and automatically generates solvers.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We do so by translating equations from vector calculus notation to the \"discrete exterior calculus\" (DEC). This process is roughly about recognizing whether physical quantities represent scalar or vector quantities, and recognizing whether differential operators represent gradient, divergence, and so on.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"In this benchmark, we will implement the \"small slope approximation\" of glacial dynamics used by P. Halfar in his 1981 work \"On the dynamics of the ice sheets\" by taking his original formulation, translating it into the DEC, then providing a mesh and initial conditions.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"The initial conditions used here are exactly those considered by W. H. Lipscomb et al. in \"Description And Evaluation of the Community Ice Sheet Model (CISM) v2.1\" (2019).","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing BenchmarkTools\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point2, Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nusing Statistics\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64}\nnothing # hide","category":"page"},{"location":"cism/cism/#Specifying-and-Composing-Physics","page":"CISM v2.1","title":"Specifying and Composing Physics","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Equation 2\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We will translate Halfar's equation into the DEC below. Although the equation given by Halfar is dense, this notation does not allow you to see which operators represent divergence, which components represent diffusivity constants, and so on. In the DEC, there is a small pool of operators, ⋆, d, ∧, ♯, and ♭, which combine according to set rules to encode all of these notions.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"In the DEC, gradients are generalized by the exterior derivative \"d\". Given scalar-data, d gives the slope along edges in our mesh. Similarly, the operator (⋆, d, ⋆) generalizes divergence.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"In Halfar's equation there is a term corresponding to the magnitude of the slope, but it is not clear where this quantity is to be defined. Is it a scalar-like quantity, or a vector-like quantity? In the DEC translation, we take the gradient of h, d(h), and use the \"sharp\" operator to define it on points in the domain, where we then take its magnitude. The \"wedge product\", ∧, takes care of multiplying a scalar-like quantity by a vector-like quantity.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Halfar's equation looks a little disjoint. It seems that the front most terms are responsible for computing some parameter, while the remaining terms on the right encode something about the dynamics. This is because Halfar's equation is actually describing two equations in one. The front-most term defines a quantity - depending on the strain of the ice - that controls the rate at which ice diffuses. Since this computation is rather separate from the rest of the computations involving our differential operators, we will call it \"Gamma\" here, and define it in a later component Decapode.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Equation 2 from Halfar, P. ON THE DYNAMICS OF THE ICE SHEETS. (1981),\n# translated into the exterior calculus.\nhalfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nto_graphviz(halfar_eq2)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Glen's Law\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Here, we recognize that Gamma is in fact what glaciologists call \"Glen's Flow Law.\" It states that the strain rate of a sheet of ice can be related to applied stress via a power law. Below, we encode the formulation as it is usually given in the literature, depending explicitly on the gravitational constant, g.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Equation 1 from Glen, J. W. THE FLOW LAW OF ICE: A discussion of the\n# assumptions made in glacier theory, their experimental foundations and\n# consequences. (1958)\nglens_law = @decapode begin\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nto_graphviz(glens_law)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We now need some way to compose these physics equations together. Since this physics is rather small, and there are no naming conflicts of physical quantities, this composition is also rather simple.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"#####################\n# Compose the model #\n#####################\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\ndraw_composition(ice_dynamics_composition_diagram)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plug in our Decapodes to the composition pattern.\nice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\n\nice_dynamics = apex(ice_dynamics_cospan)\nto_graphviz(ice_dynamics, verbose=false)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We have a representation of our composed physics. Now, we need to specify that these dynamics occur in 2 dimensions.","category":"page"},{"location":"cism/cism/#Providing-a-Semantics","page":"CISM v2.1","title":"Providing a Semantics","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Interpret this multiphysics diagram in the 2D exterior calculus.\n\nice_dynamics2D = expand_operators(ice_dynamics)\ninfer_types!(ice_dynamics2D)\nresolve_overloads!(ice_dynamics2D)\nto_graphviz(ice_dynamics2D, verbose=false)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We are done encoding our dynamics. Now, we need to provide a mesh, initial data to use for our quantities, and what functions to use for our differential operators.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Our mesh library, CombinatorialSpaces, can interpret arbitrary .OBJ files to run our dynamics on. Here, we use a script that generates a triangulated grid of the resolution used in the CISM benchmark. Note though that the \"resolution\" of a triangulated and non-triangulated grid is difficult to directly compare.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"s = triangulated_grid(60_000,100_000,2_000,2_000,Point3D)\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nx̄ = mean(p -> p[1], point(sd))\nȳ = mean(p -> p[2], point(sd))\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\nwf = wireframe!(ax, sd; linewidth=0.5)\nsave(\"ice_mesh.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Wireframe of the Domain\")","category":"page"},{"location":"cism/cism/#Defining-input-data","page":"CISM v2.1","title":"Defining input data","text":"","category":"section"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We replicate the initial conditions and parameter values used in the CISM benchmark.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# These are the initial conditions to the Halfar Dome test case that the\n# Community Ice Sheet Model uses.\nR₀ = 60_000 * sqrt(0.125)\nH = 2_000 * sqrt(0.125)\n\nn = 3\ng = 9.8101\nρ = 910\nalpha = 1/9\nbeta = 1/18\nflwa = 1e-16\nA = fill(1e-16, ne(sd))\n\nGamma = 2.0/(n+2) * flwa * (ρ * g)^n\nt0 = (beta/Gamma) * (7.0/4.0)^3 * (R₀^4 / H^7)\n\n# This is the analytic solution for comparison.\n# It is ported over from the CISM code for comparison's sake,\n# and we will use it to set initial conditions.\nfunction height_at_p(x,y,t)\n tr = (t + t0) / t0\n r = sqrt((x - x̄)^2 + (y - ȳ)^2)\n r = r/R₀\n inside = max(0.0, 1.0 - (r / tr^beta)^((n+1.0) / n))\n H * inside^(n / (2*n + 1)) / tr^alpha\nend\n\n# Set the initial conditions for ice sheet height:\n# Ice height is a primal 0-form. i.e. valued at vertices.\nh₀ = map(x -> height_at_p(x[1], x[2], 0), point(s))\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\nmsh = mesh!(ax, s, color=h₀, colormap=:jet)\nsave(\"ice_initial_conditions.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Initial Conditions\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Store these values to be passed to the solver.\nu₀ = ComponentArray(dynamics_h = h₀)\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We provide here the mapping from symbols to differential operators. As more of the differential operators of the DEC are implemented, they are upstreamed to the Decapodes and CombinatorialSpaces libraries. Of course, users can also provide their own implementations of these operators and others as they see fit.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"#############################################\n# Define how symbols map to Julia functions #\n#############################################\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n # We pre-allocate matrices that encode differential operators.\n op = @match my_symbol begin\n :mag => x -> norm.(x)\n :♯ => begin\n sharp_mat = ♯_mat(sd, AltPPSharp())\n x -> sharp_mat * x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"The gensim function takes our high-level representation of the physics equations and produces compiled simulation code. It performs optimizations such as allocating memory for intermediate variables, and so on.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"#######################\n# Generate simulation #\n#######################\n\nsim = eval(gensim(ice_dynamics2D))\nfₘ = sim(sd, generate)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Julia is a \"Just-In-Time\" compiled language. That means that functions are compiled the first time they are called, and later calls to those functions skip this step. To get a feel for just how fast this simulation is, we will run the dynamics twice, once for a very short timespan to trigger pre-compilation, and then again for the actual dynamics.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Pre-compile simulation\n\n# Julia will pre-compile the generated simulation the first time it is run.\n@info(\"Precompiling Solver\")\n# We run for a short timespan to pre-compile.\nprob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)\nsoln = solve(prob, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Run simulation\ntₑ = 200\n\n# This next run should be fast.\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5(), saveat=0.1)\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We can benchmark the compiled simulation with @benchmarkable. This macro runs many samples of the simulation function so we get an accurate estimate of the simulation time. The simulation time is quite fast compared to the CISM benchmarks. These results are run automatically via GitHub Actions as part of our docs build, which is not optimized for numerical simulations.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Time the simulation\n\nb = @benchmarkable solve(prob, Tsit5(), saveat=0.1)\nc = run(b)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Here we save the solution information to a file.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We recall that these dynamics are of the \"shallow slope\" and \"shallow ice\" approximations. So, at the edge of our parabolic dome of ice, we expect increased error as the slope increases. On the interior of the dome, we expect the dynamics to match more closely that given by the analytic model. We will see that the CISM results likewise accumulate error in the same neighborhood.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Small Ice Approximation Quote\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plot the final conditions\nfunction plot_final_conditions()\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1],\n title=\"Modeled thickness (m) at time 200.0\",\n aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=soln(200.0).dynamics_h, colormap=:jet)\n Colorbar(fig[1,2], msh)\n fig\nend\nfig = plot_final_conditions()\nsave(\"ice_numeric_solution.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numerical Solution\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plot the final conditions according to the analytic solution.\nfunction plot_analytic()\n hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1],\n title=\"Analytic thickness (m) at time 200.0\",\n aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=hₐ, colormap=:jet)\n Colorbar(fig[1,2], msh)\n fig\nend\nfig = plot_analytic()\nsave(\"ice_analytic_solution.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Analytic Solution)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Plot the error.\nfunction plot_error()\n hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\n h_diff = soln(tₑ).dynamics_h - hₐ\n extrema(h_diff)\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1],\n title=\"Modeled thickness - Analytic thickness at time 200.0\",\n aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=h_diff, colormap=:jet)\n Colorbar(fig[1,2], msh)\n fig\nend\nfig = plot_error()\nsave(\"ice_error.png\", fig)\nnothing # hide","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numeric Solution - Analytic Solution\")","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We compute below that the maximum absolute error is approximately 89 meters. We observe that this error occurs exactly on the edge of the dome, which we expect given that this is where the \"shallow slope approximation\" breaks down, and the updates to our physical quantities should become more unstable. This pattern likewise occurs in the CISM benchmarks.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute max absolute error:\nhₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\nh_diff = soln(tₑ).dynamics_h - hₐ\nmaximum(abs.(h_diff))","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We compute root-mean-square (RMS) error as well, both over the entire domain, and excluding where the ice distribution is 0 in the analytic solution. This is done since considering the entire domain decreases the RMS while not telling you much about the area of interest. Note that the official CISM benchmark reports 6.43 and 9.06 RMS for their two solver implementations.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMS not considering the \"outside\".\nhₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\nnonzeros = findall(!=(0), hₐ)\nh_diff = soln(tₑ).dynamics_h - hₐ\nrmse = sqrt(sum(map(x -> x*x, h_diff[nonzeros])) / length(nonzeros))","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMS of the entire domain.\nhₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s))\nh_diff = soln(tₑ).dynamics_h - hₐ\nrmse = sqrt(sum(map(x -> x*x, h_diff)) / length(h_diff))","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Create a gif\nbegin\n frames = 100\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\n msh = mesh!(ax, s, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(tₑ).dynamics_h))\n Colorbar(fig[1,2], msh)\n record(fig, \"ice_dynamics_cism.gif\", range(0.0, tₑ; length=frames); framerate = 30) do t\n msh.color = soln(t).dynamics_h\n end\nend","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Ice Dynamics)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"For comparison's sake, we paste the results produced by CISM below. We observe that the error likewise accumulates around the edge of the dome, with more accurate predictions on the interior. We note that our simulation produces slight over-estimates on the interior, but there are further strategies that one can employ to increase accuracy, such as tweaking the error tolerance of the solver, and so on.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Not that since the DEC is based on triangulated meshes, the \"resolution\" of the CISM benchmark and the Decapodes implementation cannot be directly compared. An advantage of the DEC is that we do not need to operate on uniform grids. For example, you could construct a mesh that is finer along the dome edge, where you need more resolution, and coarser as you are farther away from the reach of the ice.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: CISM Results)","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"We saw in this document how to create performant and accurate simulations in the Decapodes framework, and compared against the CISM library . Although we do not expect to be both more performant and accurate compared to every hand-crafted simulation, Decapodes makes up for this difference in terms of development time, flexibility, and composition. For example, the original implementation of the Decapodes shallow ice model took place over a couple of afternoons.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Since Decapodes targets high-level representations of physics, it is uniquely suited to incorporating knowledge from subject matter experts to increase simulation accuracy. This process does not require an ice dynamics expert to edit physics equations that have already been weaved into solver code.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"Further improvements to the Decapodes library are made continuously. We are creating implementations of DEC operators that are constructed and execute faster. And we are in the beginning stages of 3D simulations using the DEC.","category":"page"},{"location":"cism/cism/","page":"CISM v2.1","title":"CISM v2.1","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Halfar's-model-of-glacial-flow","page":"Glacial Flow","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's model glacial flow using a model of how ice height of a glacial sheet changes over time, from P. Halfar's 1981 paper: \"On the dynamics of the ice sheets\".","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point2, Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nusing Statistics\nPoint2D = Point2{Float64};\nPoint3D = Point3{Float64};","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Defining-the-models","page":"Glacial Flow","title":"Defining the models","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"The first step is to find a suitable equation for our model, and translate it into the Discrete Exterior Calculus. The Exterior Calculus is a generalization of vector calculus, so for low-dimensional spaces, this translation is straightforward. For example, divergence is typically written as (⋆, d, ⋆). Scalar fields are typically interpreted as \"0Forms\", i.e. values assigned to vertices of a mesh.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We use the @decapode macro to interpret the equations. Here, we have equation 2 from Halfar:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"fracpartial hpartial t = frac2n + 2 (fracrho gA)^n fracpartialpartial x(fracpartial hpartial x fracpartial hpartial x ^n-1 h^n+2)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We'll change the term out front to Γ so we can demonstrate composition in a moment.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"In the exterior calculus, we could write the above equations like so:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"partial_t(h) = circ(star d star)(Gammaquad d(h)quad textavg_01d(h)^sharp^n-1 quad textavg_01(h^n+2))","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"avg here is an operator that performs the midpoint rule, setting the value at an edge to be the average of the values at its two vertices.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) * avg₀₁(mag(♯(d(h)))^(n-1)) * avg₀₁(h^(n+2)))\nend\n\nto_graphviz(halfar_eq2)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"And here, a formulation of Glen's law from J.W. Glen's 1958 \"The flow law of ice\".","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"glens_law = @decapode begin\n #Γ::Form0\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nto_graphviz(glens_law)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Composing-models","page":"Glacial Flow","title":"Composing models","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We can use operadic composition to specify how our models come together. In this example, we have two Decapodes, and two quantities that are shared between them.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\ndraw_composition(ice_dynamics_composition_diagram)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"To a apply a composition, we specify which Decapodes to plug into those boxes, and what each calls the corresponding shared variables internally.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\n\nice_dynamics = apex(ice_dynamics_cospan)\nto_graphviz(ice_dynamics)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Provide-a-semantics","page":"Glacial Flow","title":"Provide a semantics","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"To interpret our composed Decapode, we need to specify what Discrete Exterior Calculus to interpret our quantities in. Let's choose the 1D Discrete Exterior Calculus:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics1D = expand_operators(ice_dynamics)\ninfer_types!(ice_dynamics1D, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(ice_dynamics1D, op1_res_rules_1D, op2_res_rules_1D)\n\nto_graphviz(ice_dynamics1D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-a-mesh","page":"Glacial Flow","title":"Define a mesh","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We'll need a mesh to simulate on. Since this is a 1D mesh, we can go ahead and make one right now:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# This is an empty 1D mesh.\ns = EmbeddedDeltaSet1D{Bool, Point2D}()\n\n# 20 vertices along a line, connected by edges.\nadd_vertices!(s, 20, point=Point2D.(range(0, 10_000, length=20), 0))\nadd_edges!(s, 1:nv(s)-1, 2:nv(s))\norient!(s)\n\n# The dual 1D mesh\nsd = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s)\nsubdivide_duals!(sd, Circumcenter())","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-input-data","page":"Glacial Flow","title":"Define input data","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We need initial conditions to use for our simulation.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"n = 3\nρ = 910\ng = 9.8\nA = 1e-16\n\n# Ice height is a primal 0-form, with values at vertices.\n# We choose a distribution that obeys the shallow height and shallow slope conditions.\nh₀ = map(point(s)) do (x,_)\n ((7072-((x-5000)^2))/9e3+2777)/2777e-1\nend\n\n# Visualize initial conditions for ice sheet height.\nlines(map(x -> x[1], point(s)), h₀, linewidth=5)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We need to tell our Decapode which data maps to which symbols. We can wrap up our data like so:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"u₀ = ComponentArray(dynamics_h=h₀)\n\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-functions","page":"Glacial Flow","title":"Define functions","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"In order to solve our equations, we will need numerical linear operators that give meaning to our symbolic operators. In the DEC, there are a handful of operators that one uses to construct all the usual vector calculus operations, namely: ♯, ♭, ∧, d, ⋆. The CombinatorialSpaces.jl library specifies many of these for us.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :♯ => x -> begin\n # This is an implementation of the \"sharp\" operator from the exterior\n # calculus, which takes co-vector fields to vector fields.\n # This could be up-streamed to the CombinatorialSpaces.jl library. (i.e.\n # this operation is not bespoke to this simulation.)\n e_vecs = map(edges(sd)) do e\n point(sd, sd[e, :∂v0]) - point(sd, sd[e, :∂v1])\n end\n neighbors = map(vertices(sd)) do v\n union(incident(sd, v, :∂v0), incident(sd, v, :∂v1))\n end\n n_vecs = map(neighbors) do es\n [e_vecs[e] for e in es]\n end\n map(neighbors, n_vecs) do es, nvs\n sum([nv*norm(nv)*x[e] for (e,nv) in zip(es,nvs)]) / sum(norm.(nvs))\n end\n end\n :mag => x -> norm.(x)\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Generate-the-simulation","page":"Glacial Flow","title":"Generate the simulation","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Now, we have everything we need to generate our simulation:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics1D, dimension=1))\nfₘ = sim(sd, generate)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Pre-compile-and-run","page":"Glacial Flow","title":"Pre-compile and run","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"The first time that you run a function, Julia will pre-compile it, so that later runs will be fast. We'll solve our simulation for a short time span, to trigger this pre-compilation, and then run it.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@info(\"Precompiling Solver\")\nprob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)\nsoln = solve(prob, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")\n\ntₑ = 8_000\n\n# This next run should be fast.\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We can save our solution file in case we want to examine its contents when this Julia session ends.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics1D.jld2\" soln","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Visualize","page":"Glacial Flow","title":"Visualize","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's examine the final conditions:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"fig,ax,ob = lines(map(x -> x[1], point(s)), soln(tₑ).dynamics_h, linewidth=5)\nylims!(ax, extrema(h₀))\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We see that our distribution converges to a more uniform ice height across our domain, which matches our physical intuition.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's create a GIF to examine an animation of these dynamics:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# Create a gif\nbegin\n frames = 100\n fig, ax, ob = lines(map(x -> x[1], point(s)), soln(0).dynamics_h)\n ylims!(ax, extrema(h₀))\n record(fig, \"ice_dynamics1D.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n lines!(map(x -> x[1], point(s)), soln(t).dynamics_h)\n end\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics1D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#2D-Re-interpretation","page":"Glacial Flow","title":"2D Re-interpretation","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"The first, one-dimensional, semantics that we provided to our Decapode restricted the kinds of glacial sheets that we could model. (i.e. We could only look at glacial sheets which were constant along y). We can give our Decapode an alternate semantics, as some physics on a 2-dimensional manifold.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Note that for these physics, we make no adjustments to the underlying \"dimension-agnostic\" Decapode, we only provide a different set of rules for inferring what the type of each quantity is.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics2D = expand_operators(ice_dynamics)\ninfer_types!(ice_dynamics2D)\nresolve_overloads!(ice_dynamics2D)\n\nto_graphviz(ice_dynamics2D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Store-as-JSON","page":"Glacial Flow","title":"Store as JSON","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We quickly demonstrate how to serialize a Decapode to JSON and read it back in:","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"write_json_acset(ice_dynamics2D, \"ice_dynamics2D.json\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"You can view the JSON file here.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# When reading back in, we specify that all attributes are \"Symbol\"s.\nice_dynamics2 = read_json_acset(SummationDecapode{Symbol,Symbol,Symbol}, \"ice_dynamics2D.json\")\n# Or, you could choose to interpret the data as \"String\"s.\nice_dynamics3 = read_json_acset(SummationDecapode{String,String,String}, \"ice_dynamics2D.json\")\n\nto_graphviz(ice_dynamics3)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-our-mesh","page":"Glacial Flow","title":"Define our mesh","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"s = triangulated_grid(10_000,10_000,800,800,Point3D)\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-our-input-data","page":"Glacial Flow","title":"Define our input data","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"n = 3\nρ = 910\ng = 9.8\nA = 1e-16\n\n# Ice height is a primal 0-form, with values at vertices.\nh₀ = map(point(s)) do (x,y)\n (7072-((x-5000)^2 + (y-5000)^2)^(1/2))/9e3+10\nend\n\n# Visualize initial condition for ice sheet height.\nmesh(s, color=h₀, colormap=:jet)\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nmsh = mesh!(ax, s, color=h₀, colormap=:jet)\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"u₀ = ComponentArray(dynamics_h=h₀)\n\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Define-our-functions","page":"Glacial Flow","title":"Define our functions","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :♯ => begin\n sharp_mat = ♯_mat(sd, AltPPSharp())\n x -> sharp_mat * x\n end\n :mag => x -> norm.(x)\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Generate-simulation","page":"Glacial Flow","title":"Generate simulation","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(sd, generate)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Pre-compile-and-run-2D","page":"Glacial Flow","title":"Pre-compile and run 2D","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@info(\"Precompiling Solver\")\n# We run for a short timespan to pre-compile.\nprob = ODEProblem(fₘ, u₀, (0, 1e-8), constants_and_parameters)\nsoln = solve(prob, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"tₑ = 5e13\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"ice_dynamics/ice_dynamics/#Visualize-2D","page":"Glacial Flow","title":"Visualize 2D","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# Final conditions:\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nmsh = mesh!(ax, s, color=soln(tₑ).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"begin\n frames = 100\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1])\n msh = CairoMakie.mesh!(ax, s, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\n Colorbar(fig[1,2], msh)\n record(fig, \"ice_dynamics2D.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n msh.color = soln(t).dynamics_h\n end\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2D)","category":"page"},{"location":"ice_dynamics/ice_dynamics/#2-Manifold-in-3D","page":"Glacial Flow","title":"2-Manifold in 3D","text":"","category":"section"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We note that just because our physics is happening on a 2-manifold, (a surface), this doesn't restrict us to the 2D plane. In fact, we can \"embed\" our 2-manifold in 3D space to simulate a glacial sheets spread across the globe.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"s = loadmesh(Icosphere(3, 10_000))\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nwireframe(sd)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"n = 3\nρ = 910\ng = 9.8\nA = 1e-16\n\n# Ice height is a primal 0-form, with values at vertices.\nh₀ = map(point(s)) do (x,y,z)\n (z*z)/(10_000*10_000)\nend\n\n# Visualize initial condition for ice sheet height.\n# There is lots of ice at the poles, and no ice at the equator.\nfig = Figure()\nax = LScene(fig[1,1], scenekw=(lights=[],))\nmsh = CairoMakie.mesh!(ax, s, color=h₀, colormap=:jet)\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"u₀ = ComponentArray(dynamics_h=h₀)\n\nconstants_and_parameters = (\n n = n,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(sd, generate)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"For brevity's sake, we'll skip the pre-compilation cell.","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"tₑ = 5e25\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")\n\n# Compare the extrema of the initial and final conditions of ice height.\nextrema(soln(0).dynamics_h), extrema(soln(tₑ).dynamics_h)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"fig = Figure()\nax = LScene(fig[1,1], scenekw=(lights=[],))\nmsh = CairoMakie.mesh!(ax, s, color=soln(tₑ).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\nColorbar(fig[1,2], msh)\nfig","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"begin\n frames = 200\n fig = Figure()\n ax = LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.mesh!(ax, s, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\n\n Colorbar(fig[1,2], msh)\n # These particular initial conditions diffuse quite quickly, so let's just look at\n # the first moments of those dynamics.\n record(fig, \"ice_dynamics2D_sphere.gif\", range(0.0, tₑ/64; length=frames); framerate = 20) do t\n msh.color = soln(t).dynamics_h\n end\nend","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2DSphere)","category":"page"},{"location":"ice_dynamics/ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"ch/cahn-hilliard/#The-Cahn-Hilliard-Equation","page":"Cahn-Hilliard","title":"The Cahn-Hilliard Equation","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"For this example, Decapodes will model the Cahn-Hilliard equation. This equation describes the evolution of a binary fluid as its two phases separate out into distinct domains. Below is a high resolution preview of this model. Notice how the fluid has separated into distinct regions (blue and red) as well as the presence of a transition region.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"Cahn Hilliard sample\")","category":"page"},{"location":"ch/cahn-hilliard/#Formulating-the-Equation","page":"Cahn-Hilliard","title":"Formulating the Equation","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"We first load in our dependencies","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing Random\nPoint3D = Point3{Float64};\nnothing #hide","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"and then proceed to describe our physics using Decapodes.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"CahnHilliard = @decapode begin\n C::Form0\n (D, γ)::Constant\n ∂ₜ(C) == D * Δ(C.^3 - C - γ * Δ(C))\nend\n\nto_graphviz(CahnHilliard)","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"In this equation C will represent the concentration of the binary fluid, ranging from -1 to 1 to differentiate between different phases. We also have a diffusion constant D and a constant γ whose square root is the length of the transition regions. This formulation of the Cahn-Hilliard equation was drawn from the Wikipedia page on the topic found here.","category":"page"},{"location":"ch/cahn-hilliard/#Loading-the-Data","page":"Cahn-Hilliard","title":"Loading the Data","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"We now generate the mesh information. We'll run the equation on a triangulated grid. We hide the mesh visualization code for clarity.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"s = triangulated_grid(100, 100, 0.5, 0.5, Point3D);\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s);\nsubdivide_duals!(sd, Circumcenter());\nfig = Figure() # hide\nax = CairoMakie.Axis(fig[1,1], aspect=1) # hide\nwf = wireframe!(ax, s; linewidth=1) # hide\nsave(\"CahnHilliard_Rect.png\", fig) # hide\nnothing # hide","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"CahnHilliardRect\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"The Cahn-Hilliard equation starts with a random concentration holding values between -1 and 1. For both D and γ constants we choose 0.5.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"Random.seed!(0)\n\nC = rand(Float64, nv(sd)) * 2 .- 1\nu₀ = ComponentArray(C=C)\nconstants = (D = 0.5, γ = 0.5);\n\nfig = Figure() # hide\nax = CairoMakie.Axis(fig[1,1], aspect=1) # hide\nmsh = CairoMakie.mesh!(ax, s, color=C, colormap=:jet, colorrange=extrema(C)) # hide\nColorbar(fig[1,2], msh)\nsave(\"CahnHilliard_initial.png\", fig) # hide\nnothing # hide","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"Initial conditions\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"We'll now create the simulation code representing the Cahn-Hilliard equation. We pass nothing in the second argument to sim since we have no custom functions to pass in.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"sim = eval(gensim(CahnHilliard))\nfₘ = sim(sd, nothing, DiagonalHodge());","category":"page"},{"location":"ch/cahn-hilliard/#Getting-the-Solution","page":"Cahn-Hilliard","title":"Getting the Solution","text":"","category":"section"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"Now that everything is set up and ready, we can solve the equation. We run the simulation for 200 time units to see the long-term evolution of the fluid. Note we only save the solution at intervals of 0.1 time units in order to reduce the memory-footprint of the solve.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"tₑ = 200\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants)\nsoln = solve(prob, Tsit5(), saveat=0.1);\nsoln.retcode","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"And we can see the result as a gif.","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"function create_gif(solution, file_name)\n frames = 200\n fig = Figure()\n ax = CairoMakie.Axis(fig[1,1])\n msh = CairoMakie.mesh!(ax, s, color=solution(0).C, colormap=:jet, colorrange=extrema(solution(0).C))\n Colorbar(fig[1,2], msh)\n CairoMakie.record(fig, file_name, range(0.0, tₑ; length=frames); framerate = 15) do t\n msh.color = solution(t).C\n end\nend\ncreate_gif(soln, \"CahnHilliard_Rect.gif\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"(Image: \"CahnHilliardRes\")","category":"page"},{"location":"ch/cahn-hilliard/","page":"Cahn-Hilliard","title":"Cahn-Hilliard","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"nhs/nhs_lite/#Implement-Oceananigans.jl's-NonhydrostaticModel-in-the-Discrete-Exterior-Calculus","page":"NHS","title":"Implement Oceananigans.jl's NonhydrostaticModel in the Discrete Exterior Calculus","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Let's use Decapodes to implement the NonhydrostaticModel from Oceananigans.jl. We will take the opportunity to demonstrate how we can use our \"algebra of model compositions\" to encode certain guarantees on the models we generate. We will use the 2D Turbulence as a guiding example, and use only equations found in the Oceananigans docs to construct our model.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"The full code that generated these results is available in a julia script.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing Downloads\nusing GeometryBasics: Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nPoint3D = Point3{Float64};\nnothing # hide","category":"page"},{"location":"nhs/nhs_lite/#Specify-our-models","page":"NHS","title":"Specify our models","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 1: \"The momentum conservation equation\". This is the first formulation of mutual advection (of v along V, and V along v) that we could find in the exterior calculus.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"momentum = @decapode begin\n (v,V)::DualForm1\n f::Form0\n uˢ::DualForm1\n ∂tuˢ::DualForm1\n p::DualForm0\n b::DualForm0\n ĝ::DualForm1\n Fᵥ::DualForm1\n StressDivergence::DualForm1\n\n ∂ₜ(v) ==\n -ℒ₁(v,v) + 0.5*d(ι₁₁(v,v)) -\n d(ι₁₁(v,V)) + ι₁₂(v,d(V)) + ι₁₂(V,d(v)) -\n (f - ∘(d,⋆)(uˢ)) ∧ᵖᵈ₀₁ v -\n d(p) +\n b ∧ᵈᵈ₀₁ ĝ -\n StressDivergence +\n ∂tuˢ +\n Fᵥ\nend\nto_graphviz(momentum)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Why did we write \"StressDivergence\" instead of ∇⋅τ, as in the linked equation? According to this docs page, the user makes a selection of what model to insert in place of the term ∇⋅τ. For example, in the isotropic case, Oceananigans.jl replaces this term with: ∇⋅τ = νΔv. Thus, we write StressDivergence, and replace this term with a choice of \"turbulence closure\" model. Using the \"constant isotropic diffusivity\" case, we can operate purely in terms of scalar-valued forms.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 2: \"The tracer conservation equation\".","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"tracer_conservation = @decapode begin\n (c,C,F,FluxDivergence)::DualForm0\n (v,V)::DualForm1\n\n ∂ₜ(c) ==\n -1*ι₁₁(v,d(c)) -\n ι₁₁(V,d(c)) -\n ι₁₁(v,d(C)) -\n FluxDivergence +\n F\nend\nto_graphviz(tracer_conservation)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Linear equation of state\" of seawater buoyancy.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"equation_of_state = @decapode begin\n (b,T,S)::DualForm0\n (g,α,β)::Constant\n\n b == g*(α*T - β*S)\nend\nto_graphviz(equation_of_state)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Constant isotropic diffusivity\".","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"isotropic_diffusivity = @decapode begin\n v::DualForm1\n c::DualForm0\n StressDivergence::DualForm1\n FluxDivergence::DualForm0\n (κ,nu)::Constant\n\n StressDivergence == nu*Δᵈ₁(v)\n FluxDivergence == κ*Δᵈ₀(c)\nend\nto_graphviz(isotropic_diffusivity)","category":"page"},{"location":"nhs/nhs_lite/#Compatibility-Guarantees-via-Operadic-Composition","page":"NHS","title":"Compatibility Guarantees via Operadic Composition","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Decapodes composition is formally known as an \"operad algebra\". That means that we don't have to encode our composition in a single undirected wiring diagram (UWD) and then apply it. Rather, we can define several UWDs, compose those, and then apply those. Of course, since the output of oapply is another Decapode, we could perform an intermediate oapply, if that is convenient.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Besides it being convenient to break apart large UWDs into component UWDs, this hierarchical composition can enforce rules on our physical quantities.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"For example:","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"We want all the tracers (salinity, temperature, etc.) in our physics to obey the same conservation equation.\nWe want them to obey the same \"turbulence closure\", which affects their flux-divergence term.\nAt the same time, a choice of turbulence closure doesn't just affect (each of) the flux-divergence terms, it also constrains which stress-divergence is physically valid in the momentum equation.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"We will use our operad algebra to guarantee model compatibility and physical consistency, guarantees that would be burdensome to fit into a one-off type system.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Here, we specify the equations that any tracer obeys:","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"tracer_composition = @relation () begin\n # \"The turbulence closure selected by the user determines the form of ... diffusive flux divergence\"\n turbulence(FD,v,c)\n\n continuity(FD,v,c)\nend\ndraw_composition(tracer_composition)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Let's \"lock in\" isotropic diffusivity by doing an intermediate oapply.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"isotropic_tracer = apex(oapply(tracer_composition, [\n Open(isotropic_diffusivity, [:FluxDivergence, :v, :c]),\n Open(tracer_conservation, [:FluxDivergence, :v, :c])]))\nto_graphviz(isotropic_tracer)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"Let's use this building-block tracer physics at the next level. The quotes that appear in this composition diagram appear directly in the Oceananigans.jl docs.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"nonhydrostatic_composition = @relation () begin\n # \"The turbulence closure selected by the user determines the form of stress divergence\"\n # => Note that the StressDivergence term, SD, is shared by momentum and all the tracers.\n momentum(V, v, b, SD)\n\n # \"Both T and S obey the tracer conservation equation\"\n # => Temperature and Salinity both receive a copy of the tracer physics.\n temperature(V, v, T, SD, nu)\n salinity(V, v, S, SD, nu)\n\n # \"Buoyancy is determined from a linear equation of state\"\n # => The b term in momentum is that described by the equation of state here.\n eos(b, T, S)\nend\ndraw_composition(nonhydrostatic_composition)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"isotropic_nonhydrostatic_buoyancy = apex(oapply(nonhydrostatic_composition, [\n Open(momentum, [:V, :v, :b, :StressDivergence]),\n Open(isotropic_tracer, [:continuity_V, :v, :c, :turbulence_StressDivergence, :turbulence_nu]),\n Open(isotropic_tracer, [:continuity_V, :v, :c, :turbulence_StressDivergence, :turbulence_nu]),\n Open(equation_of_state, [:b, :T, :S])]));\nto_graphviz(isotropic_nonhydrostatic_buoyancy)","category":"page"},{"location":"nhs/nhs_lite/#Our-Mesh","page":"NHS","title":"Our Mesh","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"We execute these dynamics on the torus explicitly, instead of using a square with periodic boundary conditions.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"# This is a torus with resolution of its dual mesh similar to that\n# used by Oceananigans (explicitly represented as a torus, not as a\n# square with periodic boundary conditions!)\nDownloads.download(\"https://cise.ufl.edu/~luke.morris/torus.obj\", \"torus.obj\")\ns = EmbeddedDeltaSet2D(\"torus.obj\")\nsd = EmbeddedDeltaDualComplex2D{Bool,Float64,Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nfig = Figure() # hide\nax = CairoMakie.Axis(fig[1,1], aspect=1) # hide\nwf = wireframe!(ax, s; linewidth=1) # hide\nsave(\"NHS_mesh.png\", fig) # hide\nnothing # hide","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"(Image: \"NHS_torus\")","category":"page"},{"location":"nhs/nhs_lite/#Results","page":"NHS","title":"Results","text":"","category":"section"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"In the DEC, vorticity is encoded with d⋆, and speed can be encoded with norm ♯. We can use our operators from CombinatorialSpaces.jl to create our GIFs.","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"(Image: Vorticity)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"(Image: Speed)","category":"page"},{"location":"nhs/nhs_lite/","page":"NHS","title":"NHS","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"poiseuille/poiseuille/#Poissuille-Flow-for-Fluid-Mechanics","page":"Pipe Flow","title":"Poissuille Flow for Fluid Mechanics","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"When modeling a fluid flowing in a pipe, one can ignore the multidimensional structure of the pipe and approximate the system as a 1 dimensional flow along the pipe. The no-slip boundary condition and the geometry of the pipe enter a 1D equation in the form of a resistance term.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# Julia community libraries\nusing CairoMakie\nusing GeometryBasics: Point3\nusing LinearAlgebra\nusing OrdinaryDiffEq\nPoint3D = Point3{Float64}\nnothing # hide","category":"page"},{"location":"poiseuille/poiseuille/#Creating-the-Poiseuille-Equations","page":"Pipe Flow","title":"Creating the Poiseuille Equations","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"The @decapode macro creates the data structure representing the equations of Poiseuille flow. The first block declares variables, the second block defines intermediate terms and the last block is the core equation.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"For these physics, μ̃ represents the negative viscosity per unit area while R represents the drag of the pipe boundary.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Poise = @decapode begin\n P::Form0\n q::Form1\n (R, μ̃ )::Constant\n\n # Laplacian of q for the viscous effect\n Δq == Δ(q)\n # Gradient of P for the pressure driving force\n ∇P == d(P)\n\n # Definition of the time derivative of q\n ∂ₜ(q) == q̇\n\n # The core equation\n q̇ == μ̃ * ∂q(Δq) + ∇P + R * q\nend\n\nPoise = expand_operators(Poise)\ninfer_types!(Poise, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(Poise, op1_res_rules_1D, op2_res_rules_1D)\nto_graphviz(Poise)","category":"page"},{"location":"poiseuille/poiseuille/#Defining-the-Semantics","page":"Pipe Flow","title":"Defining the Semantics","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"In order to solve our equations, we will need numerical linear operators that give meaning to our symbolic operators. The generate function below assigns the necessary matrices as definitions for the symbols. In order to define the viscosity effect correctly we have to identify boundary edges and apply a mask. This is because the DEC has discrete dual cells at the boundaries that need to be handled specially for the viscosity term. We found empirically that if you allow nonzero viscosity at the boundary edges, the flows at the boundaries will be incorrect. You can find the file for boundary conditions here.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using MLStyle\ninclude(\"../boundary_helpers.jl\")\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :∂q => x -> begin\n x[boundary_edges(sd)] .= 0\n x\n end\n :∂ρ => ρ -> begin\n ρ[1] = 0\n ρ[end] = 0\n ρ\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend","category":"page"},{"location":"poiseuille/poiseuille/#A-Single-Pipe-Segment","page":"Pipe Flow","title":"A Single Pipe Segment","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"This simulation can be validated with the Poiseuille equation for a single pipe. First we create a mesh with one pipe segment.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"s = EmbeddedDeltaSet1D{Bool,Point3D}()\nadd_vertices!(s, 2, point=[Point3D(-1, 0, 0), Point3D(+1, 0, 0)])\nadd_edge!(s, 1, 2, edge_orientation=true)\n\nsd = EmbeddedDeltaDualComplex1D{Bool,Float64,Point3D}(s)\nsubdivide_duals!(sd, Circumcenter())\nsd","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using ComponentArrays\nsim = eval(gensim(Poise, dimension=1))\nfₘ = sim(sd, generate)\nq = [2.0]\nP = [10.0, 5.0]\nu = ComponentArray(q=q,P=P)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\nprob = ODEProblem(fₘ, u, (0.0, 10000.0), params)\nsoln = solve(prob, Tsit5())\nsoln.u","category":"page"},{"location":"poiseuille/poiseuille/#A-Linear-Pipe-with-Multiple-Segments","page":"Pipe Flow","title":"A Linear Pipe with Multiple Segments","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"We then move on to a linear sequence of pipe segments. You can visualize this as the discretization of a single long pipe into n segments. First we define the mesh:","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"function linear_pipe(n::Int)\n s = EmbeddedDeltaSet1D{Bool,Point3D}()\n add_vertices!(s, n, point=[Point3D(i, 0, 0) for i in 1:n])\n add_edges!(s, 1:n-1, 2:n, edge_orientation=true)\n sd = EmbeddedDeltaDualComplex1D{Bool,Float64,Point3D}(s)\n subdivide_duals!(sd, Circumcenter())\n sd\nend\n\nsd = linear_pipe(10)\nnothing # hide","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equation. Notice that the equilibrium flow is constant down the length of the pipe. This must be true because of conservation of mass. The segments are all the same length and the total flow in must equal the total flow out of each segment.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Note that we do not generate new simulation code for Poiseuille flow with gensim again. We simply need to provide our new mesh so that our discrete differential operators can be re-instantiated.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"fₘ = sim(sd, generate)\nP = [9,8,7,6,5,4,3,2,1,0]\nq = [5,3,4,2,5,2,8,4,3]\nu = ComponentArray(q=q,P=P)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\nprob = ODEProblem(fₘ, u, (0.0, 10000.0), params)\nsol = solve(prob, Tsit5());\nsol.u","category":"page"},{"location":"poiseuille/poiseuille/#A-Distribution-Network","page":"Pipe Flow","title":"A Distribution Network","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"To model a distribution network, such as residential drinking water system, we will build a binary tree of pipes that at each junction have a bifurcation into two pipes. We expect that the flow will be divided by two at each level of the tree. First we make the mesh.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"function binary_pipe(depth::Int)\n s = EmbeddedDeltaSet1D{Bool,Point3D}()\n add_vertex!(s, point=Point3D(0, 0, 0))\n for n in 1:depth\n for prev_v in vertices(s)[end-2^(n-1)+1:end]\n x, y, _ = s[:point][prev_v]\n vs = add_vertices!(s, 2, point=[Point3D(sgn*3^0.5 + x, y+1, 0)\n for sgn in [1,-1]])\n add_edges!(s, vs, [prev_v,prev_v], edge_orientation=true)\n end\n end\n v = add_vertex!(s, point=Point3D(3^0.5, -1, 0))\n add_edge!(s, 1, v, edge_orientation=true)\n sd = EmbeddedDeltaDualComplex1D{Bool,Float64,Point3D}(s)\n subdivide_duals!(sd, Circumcenter())\n sd\nend\nsd = binary_pipe(2)\nnothing # hide","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"fₘ = sim(sd, generate)\nP = collect(1.0:nv(sd))\nq = fill(5.0, ne(sd))\nu = ComponentArray(q=q,P=P)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\nprob = ODEProblem(fₘ, u, (0.0, 10000.0), params)\nsol = solve(prob, Tsit5())\nsol.u","category":"page"},{"location":"poiseuille/poiseuille/#Multiphysics","page":"Pipe Flow","title":"Multiphysics","text":"","category":"section"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Decapodes really shines when you want to extend or refine your physics. We will change our physics by adding in a term for density of the material and the corresponding changes in pressure. This is not the only formulation for including a dynamic pressure effect into this system. If you can think of a better way to include this effect, we invite you to try it as an exercise!","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Because the pressure is no longer being supplied as a parameter of the system controlled by the operators, we need to introduce a density term and a boundary condition for that density. In this system you can think of forcing a prescribed amount of material per unit time through the openings of the pipe and allowing the flow q and the pressure P to fluctuate. Before we were enforcing a fixed pressure gradient and and letting the flow fluctuate to achieve equilibrium. In the prior model, we were not accounting for the amount of material that had to flow in order to achieve that (flow, pressure) combination.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"The Decapode can be visualized with Graphviz, note that the boundary conditions are explicitly represented in the Decapode as operators that implement a masking operation. This is not consistent with the Diagrammatic Equations in Physics paper. This approach is more directly tied to the computational method and will eventually be replaced with one based on morphisms of diagrams.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"# μ̃ = negative viscosity per unit area\n# R = drag of pipe boundary\n# k = pressure as a function of density\nPoise = @decapode begin\n q::Form1\n (P, ρ)::Form0\n (k, R, μ̃ )::Constant\n\n # Poiseuille Flow\n ∂ₜ(q) == q̇\n ∇P == d(P)\n q̇ == μ̃ * ∂q(Δ(q)) - ∇P + R * q\n \n # Pressure/Density Coupling\n P == k * ρ\n ∂ₜ(ρ) == ρ̇\n ρ_up == ∘(⋆, d, ⋆)(-1 * ∧₀₁(ρ,q)) # advection\n \n # Boundary conditions\n ρ̇ == ∂ρ(ρ_up)\nend\n\nPoise = expand_operators(Poise)\ninfer_types!(Poise, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(Poise, op1_res_rules_1D, op2_res_rules_1D)\nto_graphviz(Poise)","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we can create the mesh and solve the equation.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"sd = linear_pipe(20)\n\nsim = eval(gensim(Poise, dimension=1))\nfunc = sim(sd, generate)\n\nq = [5,3,4,2,5,2,3,4,3, 10,9,8,7,6,5,5,5,5,5]\nρ = [5,3,4,2,5,2,3,4,3, 10,9,8,7,6,5,5,5,5,5,5]\nu = ComponentArray(q=q,ρ=ρ)\nparams = (k = -0.01, μ̃ = 0.5, R=0.005)\n\nprob = ODEProblem(func, u, (0.0, 10000.0), params)\nsol = solve(prob, Tsit5())\nsol.u","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Notice that the solution contains both a vector of flows and a vector of pressures.","category":"page"},{"location":"poiseuille/poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Budko-Sellers-Halfar","page":"Budyko-Sellers-Halfar","title":"Budko-Sellers-Halfar","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\",\"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"In this example, we will compose the Budyko-Sellers 1D energy balance model of the Earth's surface temperature with the Halfar model of glacial dynamics. Note that each of these components models is itself a composition of smaller physical models. In this walkthrough, we will compose them together using the same techniques.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point2\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nPoint2D = Point2{Float64};\nnothing # hide","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We have defined the Halfar ice model in other docs pages, and so will quickly define it here.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nglens_law = @decapode begin\n Γ::Form1\n A::Form1\n (ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\nice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\nhalfar = apex(ice_dynamics_cospan)\n\nto_graphviz(halfar, verbose=false)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We will introduce the Budyko-Sellers energy balance model in more detail. First, let's define the composite physics. We will visualize them all in a single diagram without any composition at first:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"energy_balance = @decapode begin\n (Tₛ, ASR, OLR, HT)::Form0\n (C)::Constant\n\n Tₛ̇ == ∂ₜ(Tₛ) \n\n Tₛ̇ == (ASR - OLR + HT) ./ C\nend\n\nabsorbed_shortwave_radiation = @decapode begin\n (Q, ASR)::Form0\n α::Constant\n\n ASR == (1 .- α) .* Q\nend\n\noutgoing_longwave_radiation = @decapode begin\n (Tₛ, OLR)::Form0\n (A,B)::Constant\n\n OLR == A .+ (B .* Tₛ)\nend\n\nheat_transfer = @decapode begin\n (HT, Tₛ)::Form0\n (D,cosϕᵖ,cosϕᵈ)::Constant\n\n HT == (D ./ cosϕᵖ) .* ⋆(d(cosϕᵈ .* ⋆(d(Tₛ))))\nend\n\ninsolation = @decapode begin\n Q::Form0\n cosϕᵖ::Constant\n\n Q == 450 * cosϕᵖ\nend\n\nto_graphviz(oplus([energy_balance, absorbed_shortwave_radiation, outgoing_longwave_radiation, heat_transfer, insolation]), directed=false)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Now let's compose the Budyko-Sellers model:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_composition_diagram = @relation () begin\n energy(Tₛ, ASR, OLR, HT)\n absorbed_radiation(Q, ASR)\n outgoing_radiation(Tₛ, OLR)\n diffusion(Tₛ, HT, cosϕᵖ)\n insolation(Q, cosϕᵖ)\nend\n\ndraw_composition(budyko_sellers_composition_diagram)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_cospan = oapply(budyko_sellers_composition_diagram,\n [Open(energy_balance, [:Tₛ, :ASR, :OLR, :HT]),\n Open(absorbed_shortwave_radiation, [:Q, :ASR]),\n Open(outgoing_longwave_radiation, [:Tₛ, :OLR]),\n Open(heat_transfer, [:Tₛ, :HT, :cosϕᵖ]),\n Open(insolation, [:Q, :cosϕᵖ])])\n\nbudyko_sellers = apex(budyko_sellers_cospan)\n\n# Save this Decapode as a JSON file\nwrite_json_acset(budyko_sellers, \"budyko_sellers.json\") \n\nto_graphviz(budyko_sellers, verbose=false)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Warming","page":"Budyko-Sellers-Halfar","title":"Warming","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We need to specify physically what it means for these two terms to interact. We will say that ice will diffuse faster as temperature increases, and will pick some coefficients that demonstrate interesting dynamics on short timescales.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"warming = @decapode begin\n Tₛ::Form0\n A::Form1\n\n A == avg₀₁(5.8282*10^(-0.236 * Tₛ)*1.65e7)\n\nend\n\nto_graphviz(warming)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Composition","page":"Budyko-Sellers-Halfar","title":"Composition","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Observe that Decapodes composition is hierarchical. This composition technique is the same as that used in composing each of the Budyko-Sellers and Halfar models.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar_composition_diagram = @relation () begin\n budyko_sellers(Tₛ)\n warming(A, Tₛ)\n halfar(A)\nend\n\ndraw_composition(budyko_sellers_halfar_composition_diagram)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We apply a composition by plugging in a Decapode for each component. We also specify the internal name of the variables to be used in combining.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar_cospan = oapply(budyko_sellers_halfar_composition_diagram,\n [Open(budyko_sellers, [:Tₛ]),\n Open(warming, [:A, :Tₛ]),\n Open(halfar, [:stress_A])])\nbudyko_sellers_halfar = apex(budyko_sellers_halfar_cospan)\n\nto_graphviz(budyko_sellers_halfar)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We can perform type inference to determine what kind of differential form each of our variables are. This is done automatically with the dimension=1 keyword given to gensim, but we will do it in-place for demonstration purposes.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar = expand_operators(budyko_sellers_halfar)\ninfer_types!(budyko_sellers_halfar, op1_inf_rules_1D, op2_inf_rules_1D)\nresolve_overloads!(budyko_sellers_halfar, op1_res_rules_1D, op2_res_rules_1D)\nto_graphviz(budyko_sellers_halfar)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Defining-the-mesh","page":"Budyko-Sellers-Halfar","title":"Defining the mesh","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"These dynamics will occur on a 1-D manifold (a line). Points near +-π/2 will represent points near the North/ South poles. Points near 0 represent those at the equator.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"s = EmbeddedDeltaSet1D{Bool, Point2D}()\nadd_vertices!(s, 100, point=Point2D.(range(-π/2 + π/32, π/2 - π/32, length=100), 0))\nadd_edges!(s, 1:nv(s)-1, 2:nv(s))\norient!(s)\nsd = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s)\nsubdivide_duals!(sd, Circumcenter())","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Define-input-data","page":"Budyko-Sellers-Halfar","title":"Define input data","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We need to supply initial conditions to our model. We will use synthetic data here.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# This is a primal 0-form, with values at vertices.\ncosϕᵖ = map(x -> cos(x[1]), point(s))\n\n# This is a dual 0-form, with values at edge centers.\ncosϕᵈ = map(edges(s)) do e\n (cos(point(s, src(s, e))[1]) + cos(point(s, tgt(s, e))[1])) / 2\nend\n\nα₀ = 0.354\nα₂ = 0.25\nα = map(point(s)) do ϕ\n α₀ + α₂*((1/2)*(3*ϕ[1]^2 - 1))\nend\nA = 210\nB = 2\nf = 0.70\nρ = 1025\ncw = 4186\nH = 70\nC = map(point(s)) do ϕ\n f * ρ * cw * H\nend\nD = 0.6\n\n# Isothermal initial conditions:\nTₛ₀ = map(point(s)) do ϕ\n 15\nend\n\n# Visualize initial condition for temperature.\nlines(map(x -> x[1], point(s)), Tₛ₀)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"n = 3\nρ = 910\ng = 9.8\n\n# Ice height is a primal 0-form, with values at vertices.\nh₀ = map(point(s)) do (x,_)\n (((x)^2)+2.5) / 1e3\nend\n\n# Visualize initial condition for ice sheet height.\nlines(map(x -> x[1], point(s)), h₀)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# Store these values to be passed to the solver.\nu₀ = ComponentArray(Tₛ=Tₛ₀, halfar_dynamics_h=h₀)\n\nconstants_and_parameters = (\n budyko_sellers_absorbed_radiation_α = α,\n budyko_sellers_outgoing_radiation_A = A,\n budyko_sellers_outgoing_radiation_B = B,\n budyko_sellers_energy_C = C,\n budyko_sellers_diffusion_D = D,\n budyko_sellers_cosϕᵖ = cosϕᵖ,\n budyko_sellers_diffusion_cosϕᵈ = cosϕᵈ,\n halfar_n = n,\n halfar_stress_ρ = ρ,\n halfar_stress_g = g)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Symbols-to-functions","page":"Budyko-Sellers-Halfar","title":"Symbols to functions","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"The symbols along edges in our Decapode must be mapped to executable functions. In the Discrete Exterior Calculus, all our operators are defined as relations between points, lines, and triangles on meshes known as simplicial sets. Thus, DEC operators are re-usable across any simplicial set.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :♯ => x -> begin\n # This is an implementation of the \"sharp\" operator from the exterior\n # calculus, which takes co-vector fields to vector fields.\n # This could be up-streamed to the CombinatorialSpaces.jl library. (i.e.\n # this operation is not bespoke to this simulation.)\n e_vecs = map(edges(sd)) do e\n point(sd, sd[e, :∂v0]) - point(sd, sd[e, :∂v1])\n end\n neighbors = map(vertices(sd)) do v\n union(incident(sd, v, :∂v0), incident(sd, v, :∂v1))\n end\n n_vecs = map(neighbors) do es\n [e_vecs[e] for e in es]\n end\n map(neighbors, n_vecs) do es, nvs\n sum([nv*norm(nv)*x[e] for (e,nv) in zip(es,nvs)]) / sum(norm.(nvs))\n end\n end\n :mag => x -> norm.(x)\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Simulation-generation","page":"Budyko-Sellers-Halfar","title":"Simulation generation","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"From our Decapode, we automatically generate a finite difference method solver that performs explicit time-stepping to solve our system of multiphysics equations.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"sim = eval(gensim(budyko_sellers_halfar, dimension=1))\nfₘ = sim(sd, generate)","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Run-simulation","page":"Budyko-Sellers-Halfar","title":"Run simulation","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We wrap our simulator and initial conditions and solve them with the stability-detection and time-stepping methods provided by DifferentialEquations.jl .","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"tₑ = 1e6\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We can save the solution file to examine later.","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"@save \"budyko_sellers_halfar.jld2\" soln","category":"page"},{"location":"bsh/budyko_sellers_halfar/#Visualize","page":"Budyko-Sellers-Halfar","title":"Visualize","text":"","category":"section"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for temperature:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"lines(map(x -> x[1], point(s)), soln(tₑ).Tₛ)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for ice height:","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"lines(map(x -> x[1], point(s)), soln(tₑ).halfar_dynamics_h)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"begin\n# Initial frame\nframes = 100\nfig = Figure()\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s))))\nylims!(ax1, extrema(soln(tₑ).Tₛ))\nax1.xlabel = \"Line plot of temperature from North to South pole, every $(tₑ/frames) time units\"\nLabel(fig[1,1,Top()], \"Surface temperature, Tₛ, [C°]\")\n\n# Animation\nrecord(fig, \"budyko_sellers_halfar_T.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n lines!(fig[1,1], map(x -> x[1], point(s)), soln(t).Tₛ)\nend\nend\n\nbegin\n# Initial frame\nframes = 100\nfig = Figure()\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s))))\nylims!(ax1, extrema(soln(tₑ).halfar_dynamics_h))\nax1.xlabel = \"Line plot of temperature from North to South pole, every $(tₑ/frames) time units\"\nLabel(fig[1,1,Top()], \"Ice height, h\")\n\n# Animation\nrecord(fig, \"budyko_sellers_halfar_h.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n lines!(fig[1,1], map(x -> x[1], point(s)), soln(t).halfar_dynamics_h)\nend\nend","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_Temperature)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_IceHeight)","category":"page"},{"location":"bsh/budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"klausmeier/klausmeier/#Klausmeier","page":"Klausmeier","title":"Klausmeier","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"include(joinpath(Base.@__DIR__, \"..\" , \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Somaliland Vegetation)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"
\n
One of the first aerial photographs of British Somaliland (now Somaliland) investigated by W.A. Macfadyen in his 1950 \"Vegetation Patterns in the Semi-Desert Plains of British Somaliland\" [1]. From this point of view, Macfadyen's \"vegetation arcs\" are plainly visible.
\n
","category":"page"},{"location":"klausmeier/klausmeier/#Background","page":"Klausmeier","title":"Background","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"From aerial photographs in the late 1940s, British ecologist W.A. Macfadyen discovered that vegetation in semi-arid environments often grows in striping patterns, but was unaware of the exact mechanism that causes them. What is especially striking about these \"vegetation arcs\" is that these stripes appear to climb uphill, with denser plant growth at the leading edge of these traveling waves. Much like how the Mandelbrot set and other interesting fractal patterns can arise from simple sets of rules, these vegetation dynamics can be explained by simple sets of partial differential equations.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The Klausmeier model, given by Christopher Klausmeier in his 1999 paper Regular and Irregular Patterns in Semiarid Vegetation[2], models such dynamics. Although Macfadyen had discovered these vegetation patterns 50s years prior[1,3], defining these dynamics through accessible and physically-meaningful PDEs proved a catalyst for further research. At the time of writing, Klausmeier's paper has been cited 594 times.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"In this document, we will use Decapodes to formally represent these equations. Moreover, we will demonstrate how one can automatically generate simulation that reproduces the dynamics given by a scientist, simply by reading in the equations given in their original publication.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The lofty goal of this document, and of Decapodes itself, is that through both explicitly representing a model - such as Klausmeier's - and handling the generation of simulation code, we can amplify its accessibility and composability, and ultimately spur further research. Lets start by using Decapodes.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier GIF)","category":"page"},{"location":"klausmeier/klausmeier/#using-Decapodes","page":"Klausmeier","title":"using Decapodes","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Load Dependencies\nusing CairoMakie\nusing Catlab\nusing CombinatorialSpaces\nusing ComponentArrays\nusing Decapodes\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Distributions\nusing GeometryBasics: Point2\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nPoint2D = Point2{Float64}\nnothing # hide","category":"page"},{"location":"klausmeier/klausmeier/#Model-Representation","page":"Klausmeier","title":"Model Representation","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The Klausmeier model consists of two parts: one governing plant growth (phytodynamics), and one governing hydrodynamics. The differential operator Δ represents the diffusion of vegetation, and the \"Lie derivative\" operator ℒ represents the change of water in a direction. In this case, the flow of water downhill, \"dX\".","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Decapodes are written in the language of the Exterior Calculus, which generalizes Vector Calculus. For us, that means that we get to specify whether a physical quantity should valued at points in our domain (i.e. primal and dual Form0s), or whether they are more vector-like quantities, that should be valued along edges in our domain (i.e. primal and dual Form1s). In this case, water density w and vegetation density n are both Form0s, while dX, the gradient of our hill, is a Form1.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# See Klausmeier Equation 2.a\nHydrodynamics = @decapode begin\n (n,w)::DualForm0\n dX::Form1\n (a,ν)::Constant\n\n ∂ₜ(w) == a - w - w * n^2 + ν * L(dX, w)\nend\n\n# See Klausmeier Equation 2.b\nPhytodynamics = @decapode begin\n (n,w)::DualForm0\n m::Constant\n\n ∂ₜ(n) == w * n^2 - m*n + Δ(n)\nend\nnothing # hide","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Now that we have our two component models, we can specify a means of composing them via a composition pattern.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Specify Composition\ncompose_klausmeier = @relation () begin\n phyto(N, W)\n hydro(N, W)\nend\n\ndraw_composition(compose_klausmeier)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We apply our composition pattern by plugging in component Decapodes, and specifying which internal quantities to share along edges. Decapodes are formalized via the field of Applied Category Theory. A practical consequence here is that we can view a Decapode as a sort of computation graph.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Apply Composition\nklausmeier_cospan = oapply(compose_klausmeier,\n [Open(Phytodynamics, [:n, :w]),\n Open(Hydrodynamics, [:n, :w])])\nKlausmeier = apex(klausmeier_cospan)\nto_graphviz(Klausmeier)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"With our model now explicitly represented, we have everything we need to automatically generate simulation code. We could write this to an intermediate file and use it later, or we can go ahead and evaluate the code in this session.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"sim = eval(gensim(Klausmeier, dimension=1))","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We now need a mesh to define our domain. In the 2D case, our CombinatorialSpaces library can read in arbitrary .OBJ files. In 1D, it is often simpler to just generate a mesh on the fly. Since we are running our physics on a circle - i.e periodic boundaries - we will use a simple function that generates it.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We will visualize the mesh embedded in two dimensions here, but in later visualizations, we can represent it as a periodic line.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Define Mesh\nfunction circle(n, c)\n s = EmbeddedDeltaSet1D{Bool, Point2D}()\n map(range(0, 2pi - (pi/(2^(n-1))); step=pi/(2^(n-1)))) do t\n add_vertex!(s, point=Point2D(cos(t),sin(t))*(c/2pi))\n end\n add_edges!(s, 1:(nv(s)-1), 2:nv(s))\n add_edge!(s, nv(s), 1)\n sd = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s)\n subdivide_duals!(sd, Circumcenter())\n s,sd\nend\ns,sd = circle(9, 500)\n\nscatter(sd[:point])","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We discretize our differential operators using the Discrete Exterior Calculus. The DEC is an elegant way of building up more complex differential operators from simpler ones. To demonstrate, we will define the Δ operator by building it up with matrix multiplication of simpler operators. Since most operators in the DEC are matrices, most simulations consist mainly of matrix-vector multiplications, and are thus very fast.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"If this code seems too low level, do not worry. Decapodes defines and caches for you many differential operators behind the scenes, so you do not have to worry about defining your own.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"lap_mat = dec_hodge_star(1,sd) * dec_differential(0,sd) * dec_inv_hodge_star(0,sd) * dec_dual_derivative(0,sd)\n\nfunction generate(sd, my_symbol; hodge=DiagonalHodge())\n op = @match my_symbol begin\n :Δ => x -> begin\n lap_mat * x\n end\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's pass our mesh and methods of generating operators to our simulation code.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Instantiate Simulation\nfₘ = sim(sd, generate, DiagonalHodge())","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"With our simulation now ready, let's specify initial data to pass to it. We'll define them with plain Julia code.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The most interesting parameter here is our \"downhill gradient\" dX. This parameter defines how steep our slope is. Since our mesh is a circle, and we are setting dX to a constant value, this means that \"downhill\" always points counter-clockwise. Essentially, this is an elegant way of encoding an infinite hill.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Define Initial Conditions\nn_dist = Normal(pi)\nn = [pdf(n_dist, t)*(√(2pi))*7.2 + 0.08 - 5e-2 for t in range(0,2pi; length=ne(sd))]\n\nw_dist = Normal(pi, 20)\nw = [pdf(w_dist, t) for t in range(0,2pi; length=ne(sd))]\n\ndX = sd[:length]\n\nu₀ = ComponentArray(N = n, W = w, hydro_dX = dX)\n\ncs_ps = (phyto_m = 0.45,\n hydro_a = 0.94,\n hydro_ν = 182.5)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's execute our simulation.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Run Simulation\ntₑ = 300.0\nprob = ODEProblem(fₘ, u₀, (0.0, tₑ), cs_ps)\nsol = solve(prob, Tsit5(), saveat=0.1, save_idxs=[:N, :W])\nsol.retcode","category":"page"},{"location":"klausmeier/klausmeier/#Animation","page":"Klausmeier","title":"Animation","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's perform some basic visualization and analysis of our results to verify our dynamics.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"n = sol(0).N\nnₑ = sol(tₑ).N\nw = sol(0).W\nwₑ = sol(tₑ).W\nnothing # hide","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Animate dynamics\nfunction save_dynamics(form_name, framerate, filename)\n time = Observable(0.0)\n ys = @lift(getproperty(sol($time), form_name))\n xcoords = [0, accumulate(+, sd[:length])[1:end-1]...]\n fig = lines(xcoords, ys, color=:green, linewidth=4.0,\n colorrange=extrema(getproperty(sol(0), form_name));\n axis = (; title = @lift(\"Klausmeier $(String(form_name)) at $($time)\")))\n timestamps = range(0, tₑ, step=1)\n record(fig, filename, timestamps; framerate=framerate) do t\n time[] = t\n end\nend\nsave_dynamics(:N, 20, \"klausmeier.gif\")","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier)","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We can observe a few interesting phenomena that we wanted to capture:","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"The vegetation density bands move uphill in traveling waves.\nThe leading edge of the waves is denser than the rest of the band.\nOver time, the periodicity of the vegetation bands stabilizes.\nThe distribution naturally emerges, despite the initial distribution is a simple normal distribution.\nThis is evidence against the real-world theory that these vegetation contours are the result of an initial (man-made) distribution.","category":"page"},{"location":"klausmeier/klausmeier/#Conclusion","page":"Klausmeier","title":"Conclusion","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Due to the ease of composition of Decapodes, representing the Klausmeier model opens up many areas for future work. For example, we can now compose these dynamics with a model of temperature dynamics informed by the Budyko-Sellers model. We can take advantage of the fact that the Lie derivative generalizes partial derivatives, and model the flow of water according to any vector field. Or, we can extend this model by composing it with a model that can recreate the so-called \"leopard pattern\" of vegetation, such as an \"Interaction-Dispersion\" model of vegetation dynamics given by Lejeune et al[4].","category":"page"},{"location":"klausmeier/klausmeier/#References","page":"Klausmeier","title":"References","text":"","category":"section"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[1] W. A. Macfadyen, “Vegetation Patterns in the Semi-Desert Plains of British Somaliland,” The Geographical Journal, vol. 116, no. 4/6, p. 199, Oct. 1950, doi: 10.2307/1789384.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[2] C. A. Klausmeier, “Regular and Irregular Patterns in Semiarid Vegetation,” Science, vol. 284, no. 5421, pp. 1826–1828, Jun. 1999, doi: 10.1126/science.284.5421.1826.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[3] W. A. Macfadyen, “Soil and Vegetation in British Somaliland,” Nature, vol. 165, no. 4186, Art. no. 4186, Jan. 1950, doi: 10.1038/165121a0.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"[4] O. Lejeune and M. Tlidi, “A Model for the Explanation of Vegetation Stripes (Tiger Bush),” Journal of Vegetation Science, vol. 10, no. 2, pp. 201–208, 1999, doi: 10.2307/3237141.","category":"page"},{"location":"klausmeier/klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"grigoriev/grigoriev/#Halfar's-model-of-glacial-flow","page":"Grigoriev Ice Cap","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"include(joinpath(Base.@__DIR__, \"..\" , \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's model glacial flow using a model of how ice height of a glacial sheet changes over time, from P. Halfar's 1981 paper: \"On the dynamics of the ice sheets\".","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's run the Halfar shallow ice / shallow slope model on some \"real world\" data for ice thickness. Van Tricht et al. in their 2023 communication Measuring and modelling the ice thickness of the Grigoriev ice cap (Kyrgyzstan) and comparison with global dataset published ice thickness data on an ice cap and stored their data in a TIF. In this document, we will demonstrate how to parse such data and execute a Decapodes model on these initial conditions.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"For the parameters to Glen's law, we will use those used in the Community Ice Sheet Model benchmark. Of course, the parameters of this Kyrgyzstani ice cap likely differ from these by quite some amount, but they are a good place to start. Further, this ice cap does not satisfy the \"shallow slope\" assumption across the entire domain.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing Decapodes\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing FileIO \nusing GeometryBasics: Point2\nusing Interpolations\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nusing SparseArrays\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64};\nnothing # hide","category":"page"},{"location":"grigoriev/grigoriev/#Loading-a-Scientific-Dataset","page":"Grigoriev Ice Cap","title":"Loading a Scientific Dataset","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The ice thickness data is stored in a TIF that can be downloaded here. We have downloaded it locally, and load it using basic FileIO.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"file_name = \"Icethickness_Grigoriev_ice_cap_2021.tif\"\nice_thickness_tif = load(file_name)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"This data may visually appear to be a binary mask but that is only because values with no ice are set to -Inf. We will account for this when interpolate our data.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"We use the Interpolations.jl library to interpolate this dataset:","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# Taking the coordinates to be from the extrema of the measured points:\nconst MIN_X = 4648894.5\nconst MAX_X = 4652179.7\nconst MIN_Y = 243504.5\nconst MAX_Y = 245599.8\nice_coords = (range(MIN_X, MAX_X, length=size(ice_thickness_tif,1)),\n range(MIN_Y, MAX_Y, length=size(ice_thickness_tif,2)))\n\n# Note that the TIF is set to -floatmax(Float32) where there is no ice.\n# For our purposes, this is equivalent to 0.0.\nice_interp = LinearInterpolation(ice_coords, Float32.(ice_thickness_tif))","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"To use this interpolating object ice_interp, we can simply query it for the value at some coordinates: ice_interp(x,y).","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's generate a triangulated grid located at the appropriate coordinates:","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# Specify a resolution:\nRES_Y = (MAX_Y-MIN_Y)/30.0\nRES_X = RES_Y\n\n# Generate the mesh with appropriate dimensions and resolution:\ns = triangulated_grid(MAX_X-MIN_X, MAX_Y-MIN_Y, RES_X, RES_Y, Point3D)\n\n# Shift it into place:\ns[:point] = map(x -> x + Point3D(MIN_X, MIN_Y, 0), s[:point])\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s)\nsave(\"Grigoriev_IceMesh.png\", fig)\nnothing # hide","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: \"Grigoriev_IceMesh\")","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The coordinates of a vertex are stored in sd[:point]. Let's use our interpolator to assign ice thickness values to each vertex in the mesh:","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# These are the values used by the CISM benchmark:\nn = 3\nρ = 910\ng = 9.8101\nA = fill(1e-16, ne(sd))\n\nh₀ = map(sd[:point]) do (x,y,_)\n tif_val = ice_interp(x,y)\n # Accommodate for the -∞'s that encode \"no ice\".\n tif_val < 0.0 ? 0.0 : tif_val\nend\n\n# Store these values to be passed to the solver.\nu₀ = ComponentArray(h=h₀, stress_A=A)\nconstants_and_parameters = (n = n, \n stress_ρ = ρ,\n stress_g = g, \n stress_A = A)\nnothing # hide","category":"page"},{"location":"grigoriev/grigoriev/#Defining-and-Composing-Models","page":"Grigoriev Ice Cap","title":"Defining and Composing Models","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"For exposition on this Halfar Decapode, see our Glacial Flow docs page. Otherwise, you may skip ahead to the next section.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ḣ == ∂ₜ(h)\n ḣ == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nglens_law = @decapode begin\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(h,Γ,n)\n stress(Γ,n)\nend\n\nice_dynamics_cospan = oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:h,:Γ,:n]),\n Open(glens_law, [:Γ,:n])])\n\nice_dynamics = apex(ice_dynamics_cospan)\nto_graphviz(ice_dynamics)","category":"page"},{"location":"grigoriev/grigoriev/#Define-our-functions","page":"Grigoriev Ice Cap","title":"Define our functions","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"function generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n :mag => x -> norm.(x)\n :♯ => begin\n sharp_mat = ♯_mat(sd, AltPPSharp())\n x -> sharp_mat * x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend","category":"page"},{"location":"grigoriev/grigoriev/#Generate-simulation","page":"Grigoriev Ice Cap","title":"Generate simulation","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"sim = eval(gensim(ice_dynamics, dimension=2))\nfₘ = sim(sd, generate)","category":"page"},{"location":"grigoriev/grigoriev/#Run","page":"Grigoriev Ice Cap","title":"Run","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"tₑ = 10\n\n@info(\"Solving Grigoriev Ice Cap\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Tsit5())\n@show soln.retcode\n@info(\"Done\")\n\n@save \"grigoriev.jld2\" soln","category":"page"},{"location":"grigoriev/grigoriev/#Results-and-Discussion","page":"Grigoriev Ice Cap","title":"Results and Discussion","text":"","category":"section"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# Visualize the initial conditions.\nfunction plot_ic()\n f = Figure()\n ax = CairoMakie.Axis(f[1,1],\n title=\"Grigoriev Ice Cap Initial Thickness [m]\",\n xticks = range(MIN_X, MAX_X; length=5),\n yticks = range(MIN_Y, MAX_Y; length=5))\n msh = mesh!(ax, s, color=soln(0.0).h, colormap=:jet)\n Colorbar(f[1,2], msh)\n f\nend\nf = plot_ic()\nsave(\"grigoriev_ic.png\", f)\n\n# Visualize the final conditions.\nfunction plot_fc()\n f = Figure()\n ax = CairoMakie.Axis(f[1,1],\n title=\"Grigoriev Ice Cap Final Thickness [m]\",\n xticks = range(MIN_X, MAX_X; length=5),\n yticks = range(MIN_Y, MAX_Y; length=5))\n msh = mesh!(ax, s, color=soln(tₑ).h, colormap=:jet)\n Colorbar(f[1,2], msh)\n f\nend\nf = plot_fc()\nsave(\"grigoriev_fc.png\", f)\n\n# Create a gif\nfunction save_dynamics(save_file_name)\n time = Observable(0.0)\n h = @lift(soln($time).h)\n f = Figure()\n ax = CairoMakie.Axis(f[1,1], title = @lift(\"Grigoriev Ice Cap Dynamic Thickness [m] at time $($time)\"))\n gmsh = mesh!(ax, s, color=h, colormap=:jet,\n colorrange=extrema(soln(tₑ).h))\n #Colorbar(f[1,2], gmsh, limits=extrema(soln(tₑ).h))\n Colorbar(f[1,2], gmsh)\n timestamps = range(0, tₑ, step=1e-1)\n record(f, save_file_name, timestamps; framerate = 15) do t\n time[] = t\n end\nend\nsave_dynamics(\"grigoriev.gif\")","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"We observe the usual Halfar model phenomena of ice \"melting\". Note that since the \"shallow slope\" approximation does not hold on the boundaries (due to the so-called \"ice cliffs\" described in the Van Tricht et al. paper), we do not expect the \"creep\" effect to be physical in this region of the domain. Rather, the Halfar model's predictive power is tuned for the interiors of ice caps and glaciers. Note that we also assume here that the bedrock that the ice rests on is flat. We may in further documents demonstrate how to use topographic data from Digital Elevation Models to inform the elevation of points in the mesh itself.","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_ICs)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_FCs)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_Dynamics)","category":"page"},{"location":"grigoriev/grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"overview/overview/#Introduction-to-Decapodes","page":"Overview","title":"Introduction to Decapodes","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Discrete Exterior Calculus Applied to Partial and Ordinary Differential Equations (Decapodes) is a diagrammatic language used to express systems of ordinary and partial differential equations. Decapodes provides a visual framework for understanding the coupling between variables within a PDE or ODE system, and a combinatorial data structure for working with them. Below, we provide a high-level overview of how Decapodes can be generated and interpreted.","category":"page"},{"location":"overview/overview/#Your-First-Decapode","page":"Overview","title":"Your First Decapode","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"In the Decapodes graphical paradigm, nodes represent variables and arrows represent operators which relate variables to each other. Since Decapodes applies this diagrammatic language specifically to the Discrete Exterior Calculus (DEC), variables are typed by the dimension and orientation of the information they contain. So a variable of type Form0 will be the 0-dimensional data points defined the vertices of a mesh. Similarly, Form1 will be values stored on edges of the mesh and Form2 will be values stored on the surfaces of the mesh.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Below, we provide a Decapode with just a single variable C and display it.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using Catlab\nusing Decapodes\nusing DiagrammaticEquations\n\nVariable = @decapode begin\n C::Form0\nend;\n\nto_graphviz(Variable)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The resulting diagram contains a single node, showing the single variable in this system. We can add a second variable:","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"TwoVariables = @decapode begin\n C::Form0\n dC::Form1\nend;\n\nto_graphviz(TwoVariables)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We can also add a relationship between them. In this case, we make an equation which states that dC is the derivative of C:","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Equation = @decapode begin\n C::Form0\n dC::Form1\n\n dC == d(C)\nend;\n\nto_graphviz(Equation)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Here, the two nodes represent the two variables, and the arrow between them shows how they are related by the derivative.","category":"page"},{"location":"overview/overview/#A-Little-More-Complicated","page":"Overview","title":"A Little More Complicated","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that we've seen how to construct a simple equation, it's time to move on to some actual PDE systems! One classic PDE example is the diffusion equation. This equation states that the change of concentration at each point is proportional to the Laplacian of the concentration.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Diffusion = @decapode begin\n (C, Ċ)::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\n\n # Diffusion equation\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\n\nend;\n\nto_graphviz(Diffusion)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The resulting Decapode shows the relationships between the three variables with the triangle diagram. Note that these diagrams are automatically layed-out by Graphviz.","category":"page"},{"location":"overview/overview/#Bring-in-the-Dynamics","page":"Overview","title":"Bring in the Dynamics","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that we have a reasonably complex PDE, we can demonstrate some of the developed tooling for actually solving the PDE. Currently, the tooling will automatically generate an explicit method for solving the system (using DifferentialEquations.jl to handle time-stepping and instability detection).","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Torus_30x10 is a default mesh that is downloaded via Artifacts.jl when a user installs CombinatorialSpaces.jl. If we wanted, we could also instantiate any .obj file of triangulated faces as a simplicial set although we do not here.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We will also upload a non-periodic mesh for the sake of visualization, as well as a mapping between the points on the periodic and non-periodic meshes.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using CairoMakie\nusing CombinatorialSpaces\n\nplot_mesh = loadmesh(Rectangle_30x10())\nperiodic_mesh = loadmesh(Torus_30x10())\npoint_map = loadmesh(Point_Map())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nwireframe!(ax, plot_mesh)\nfig","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now we create a function which links the names of functions used in the Decapode to their implementations. Note that many DEC operators are already defined for you.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"As an example, we chose to define k as a function that multiplies an input by 0.05. We could have alternately chosen to represent k as a Constant that we multiply by in the Decapode itself.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We then compile the simulation by using gen_sim and create functional simulation by calling the evaluated sim with the mesh and our generate function.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using MLStyle\n\nfunction generate(sd, my_symbol; hodge=DiagonalHodge())\n op = @match my_symbol begin\n :k => x -> 0.05*x\n x => error(\"Unmatched operator $my_symbol\")\n end\n return op\nend\n\nsim = eval(gensim(Diffusion))\nfₘ = sim(periodic_mesh, generate, DiagonalHodge())","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We go ahead and set up our initial conditions for this problem. In this case we generate a Gaussian and apply it to our mesh.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using Distributions\nc_dist = MvNormal([7, 5], [1.5, 1.5])\nc = [pdf(c_dist, [p[1], p[2]]) for p in periodic_mesh[:point]]\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\nmesh!(ax, plot_mesh; color=c[point_map])\nfig","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Finally, we solve this PDE problem using the Tsit5() solver provided by DifferentialEquations.jl.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using LinearAlgebra\nusing ComponentArrays\nusing OrdinaryDiffEq\n\nu₀ = ComponentArray(C=c)\n\nprob = ODEProblem(fₘ, u₀, (0.0, 100.0))\nsol = solve(prob, Tsit5());\nsol.retcode","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that the simulation has succeeded we can plot out our results with CairoMakie.jl.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"# Plot the result\ntimes = range(0.0, 100.0, length=150)\ncolors = [sol(t).C[point_map] for t in times]\n\n# Initial frame\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\npmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))\nColorbar(fig[1,2], pmsh)\nframerate = 30\n\n# Animation\nrecord(fig, \"diffusion.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n pmsh.color = sol(t).C[point_map]\nend","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"(Image: Your first Decapode!)","category":"page"},{"location":"overview/overview/#Merging-Multiple-Physics","page":"Overview","title":"Merging Multiple Physics","text":"","category":"section"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Now that we've seen the basic pipeline, it's time for a more complex example that demonstrates some of the benefits reaped from using Catlab.jl as the backend to our data structures. In this example, we will take two separate physics (diffusion and advection), and combine them together using a higher-level composition pattern.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We begin by defining the three systems we need. The first two systems are the relationships between concentration and flux under diffusion and advection respectively. The third is the relationship between the two fluxes and the change of concentration under superposition of fluxes.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Diffusion = @decapode begin\n C::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\nend\n\nAdvection = @decapode begin\n C::Form0\n ϕ::Form1\n V::Form1\n\n ϕ == ∧₀₁(C,V)\nend\n\nSuperposition = @decapode begin\n (C, Ċ)::Form0\n (ϕ, ϕ₁, ϕ₂)::Form1\n\n ϕ == ϕ₁ + ϕ₂\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\nend\nnothing # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The diffusion Decapode.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"to_graphviz(Diffusion) # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"The advection Decapode.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"to_graphviz(Advection) # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"And the superposition Decapode.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"to_graphviz(Superposition) # hide","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Next, we define the pattern of composition which we want to compose these physics under. This pattern of composition is described by an undirected wiring diagram, which has the individual physics as nodes and the shared variables as the small junctions.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"compose_diff_adv = @relation (C, V) begin\n diffusion(C, ϕ₁)\n advection(C, ϕ₂, V)\n superposition(ϕ₁, ϕ₂, ϕ, C)\nend\n\ndraw_composition(compose_diff_adv)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"After this, the physics can be composed as follows:","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"DiffusionAdvection_cospan = oapply(compose_diff_adv,\n [Open(Diffusion, [:C, :ϕ]),\n Open(Advection, [:C, :ϕ, :V]),\n Open(Superposition, [:ϕ₁, :ϕ₂, :ϕ, :C])])\nDiffusionAdvection = apex(DiffusionAdvection_cospan)\n\nto_graphviz(DiffusionAdvection)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"Similar to before, this physics can be compiled and executed. Note that this process now requires another value to be defined, namely the velocity vector field. We do this using a custom operator called flat_op. This operator is basically the flat operator from CombinatorialSpaces.jl, but specialized to account for the periodic mesh.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"We could instead represent the domain as the surface of an object with equivalent boundaries in 3D.","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"function closest_point(p1, p2, dims)\n p_res = collect(p2)\n for i in 1:length(dims)\n if dims[i] != Inf\n p = p1[i] - p2[i]\n f, n = modf(p / dims[i])\n p_res[i] += dims[i] * n\n if abs(f) > 0.5\n p_res[i] += sign(f) * dims[i]\n end\n end\n end\n Point3{Float64}(p_res...)\nend\n\nfunction flat_op(s::AbstractDeltaDualComplex2D, X::AbstractVector; dims=[Inf, Inf, Inf])\n tri_map = Dict{Int,Int}(triangle_center(s,t) => t for t in triangles(s))\n\n map(edges(s)) do e\n p = closest_point(point(s, tgt(s,e)), point(s, src(s,e)), dims)\n e_vec = (point(s, tgt(s,e)) - p) * sign(1,s,e)\n dual_edges = elementary_duals(1,s,e)\n dual_lengths = dual_volume(1, s, dual_edges)\n mapreduce(+, dual_edges, dual_lengths) do dual_e, dual_length\n X_vec = X[tri_map[s[dual_e, :D_∂v0]]]\n dual_length * dot(X_vec, e_vec)\n end / sum(dual_lengths)\n end\nend","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"using LinearAlgebra\nusing MLStyle\n\nfunction generate(sd, my_symbol; hodge=DiagonalHodge())\n op = @match my_symbol begin\n :k => x -> 0.05*x\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend\n\nsim = eval(gensim(DiffusionAdvection))\nfₘ = sim(periodic_mesh, generate, DiagonalHodge())\n\nvelocity(p) = [-0.5, -0.5, 0.0]\nv = flat_op(periodic_mesh, DualVectorField(velocity.(periodic_mesh[triangle_center(periodic_mesh),:dual_point])); dims=[30, 10, Inf])\n\nu₀ = ComponentArray(C=c,V=v)\n\nprob = ODEProblem(fₘ, u₀, (0.0, 100.0))\nsol = solve(prob, Tsit5());\n\n# Plot the result\ntimes = range(0.0, 100.0, length=150)\ncolors = [sol(t).C[point_map] for t in times]\n\n# Initial frame\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))\npmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))\nColorbar(fig[1,2], pmsh)\nframerate = 30\n\n# Animation\nrecord(fig, \"diff_adv.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n pmsh.color = sol(t).C[point_map]\nend","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"(Image: Your first composed Decapode!)","category":"page"},{"location":"overview/overview/","page":"Overview","title":"Overview","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"halmo/halmo/#Couple-Ice-and-Water-Dynamics","page":"Halfar-NS","title":"Couple Ice and Water Dynamics","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"include(joinpath(Base.@__DIR__, \"..\", \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's use Decapodes to implement the incompressible Navier-Stokes as given by Mohamed et al.. We will run these dynamics on the sphere. We will couple this model with Halfar glacier dynamics on the sphere. For the initial conditions of the Halfar ice thickness, we will use an idealized polar ice cap.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Note that the time scale at which ice creeps is much larger than the time scale at which the water in the ocean would flow. So we can either choose to model a very slow moving fluid around the ice (like a storm on a gas giant), or we can choose to model on a shorter timescale, on which the ice does not move very much.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\n\n# External Dependencies\nusing CairoMakie\nusing ComponentArrays\nusing GeometryBasics: Point3\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing OrdinaryDiffEq\nPoint3D = Point3{Float64};\nnothing # hide","category":"page"},{"location":"halmo/halmo/#Specify-our-models","page":"Halfar-NS","title":"Specify our models","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Our first component is the Mohamed et al. formulation of the incompressible Navier-Stokes equations. We will call the flow here \"w\". This will be the flow after collisions with glaciers are considered.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"This is Equation 10 for N=2.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"eq10forN2 = @decapode begin\n (𝐮,w)::DualForm1\n (P, 𝑝ᵈ)::DualForm0\n μ::Constant\n\n 𝑝ᵈ == P + 0.5 * ι₁₁(w,w)\n\n ∂ₜ(𝐮) == μ * ∘(d, ⋆, d, ⋆)(w) + (-1)*⋆₁⁻¹(∧ᵈᵖ₁₀(w, ⋆(d(w)))) + d(𝑝ᵈ)\nend\nto_graphviz(eq10forN2)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Halfar's equation and Glen's law are composed like so:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"halfar_eq2 = @decapode begin\n h::Form0\n Γ::Form1\n n::Constant\n\n ∂ₜ(h) == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))\nend\n\nglens_law = @decapode begin\n Γ::Form1\n (A,ρ,g,n)::Constant\n \n Γ == (2/(n+2))*A*(ρ*g)^n\nend\n\nice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\nice_dynamics = apex(oapply(ice_dynamics_composition_diagram,\n [Open(halfar_eq2, [:Γ,:n]),\n Open(glens_law, [:Γ,:n])]))\n\nto_graphviz(ice_dynamics, verbose=false)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We now have our dynamics that govern glaciers, and our dynamics that govern water. We need to specify the physics of what happens when glaciers and water interact. There are many options, and the choice you make depends on the time-scale and resolution of the dynamics that you are interested in.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"An interaction between glacier and water dynamics can look like the following, where flow_after is the flow of water after interaction with ice is considered.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"ice_water_composition_diagram = @relation () begin\n glacier_dynamics(ice_thickness)\n water_dynamics(flow, flow_after)\n\n interaction(ice_thickness, flow, flow_after)\nend\ndraw_composition(ice_water_composition_diagram)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We will use the language of Decapodes to encode the dynamics that ice blocks water from flowing.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can detect the ice with a sigmoid function. Where there is ice, we want the flow to be 0, and where there is no ice, we will not impede the flow. We won't consider any further special boundary conditions between ice and water here. Since h is a scalar-like quantity, and flow is a vector-like quantity, we can relate them using the wedge product operator from the exterior calculus. We can state these dynamics using the language of Decapodes like so:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"blocking = @decapode begin\n h::Form0\n (𝐮,w)::DualForm1\n\n w == (1-σ(h)) ∧ᵖᵈ₀₁ 𝐮\nend\nto_graphviz(blocking)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Here, σ is a sigmoid function that is 0 when d(h) is 0, and goes to 1 otherwise. We see that w is indeed defined as 𝐮, after interacting with the ice boundary is considered.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can apply our composition diagram to generate our physics:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"ice_water = apex(oapply(ice_water_composition_diagram,\n [Open(ice_dynamics, [:dynamics_h]),\n Open(eq10forN2, [:𝐮, :w]),\n Open(blocking, [:h, :𝐮, :w])]))\n\nto_graphviz(ice_dynamics, verbose=false)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can now generate our simulation:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"sim = eval(gensim(ice_water))","category":"page"},{"location":"halmo/halmo/#Meshes-and-Initial-Conditions","page":"Halfar-NS","title":"Meshes and Initial Conditions","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Since we want to demonstrate these physics on the Earth, we will use one of our icosphere discretizations with the appropriate radius.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"rₑ = 6378e3 # [km]\ns = loadmesh(Icosphere(5, rₑ))\nsd = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\nwireframe(sd)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's demonstrate how to add operators by providing the definition of a sigmoid function:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"sigmoid(x) = (2 ./ (1 .+ exp.(-x*1e2)) .- 1)\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n # This is a new function.\n :σ => sigmoid\n :mag => x -> norm.(x)\n # Remaining operations (such as our differential operators) are built-in.\n _ => default_dec_matrix_generate(sd, my_symbol, hodge)\n end\n return op\nend;","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's combine our mesh with our physics to instantiate our simulation:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"fₘ = sim(sd, generate);","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We can now supply initial conditions:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"ice_thickness = map(sd[:point]) do (_,_,z)\n z < 0.8*rₑ ? 0 : 1\nend\n\nflow = dec_dual_derivative(0,sd) *\n map(sd[sd[:tri_center], :dual_point]) do (_,_,z)\n (rₑ-abs(z))/rₑ\n end\n\n# There is no water \"under\" the ice:\nflow = dec_wedge_product_pd(Tuple{0,1},sd)(1 .- sigmoid(ice_thickness), flow)\n\nu₀ = ComponentArray(\n ice_thickness = ice_thickness,\n flow = flow,\n water_dynamics_P = zeros(ntriangles(sd)))\n\nconstants_and_parameters = (\n glacier_dynamics_n = 3,\n glacier_dynamics_stress_A = fill(1e-16, ne(sd)),\n glacier_dynamics_stress_ρ = 910,\n glacier_dynamics_stress_g = 9.8101,\n water_dynamics_μ = 0.01);","category":"page"},{"location":"halmo/halmo/#Execute-the-Simulation","page":"Halfar-NS","title":"Execute the Simulation","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"We specified our physics, our mesh, and our initial conditions. We have everything we need to execute the simulation.","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"tₑ = 100\n\n# Julia will pre-compile the generated simulation the first time it is run.\n@info(\"Precompiling Solver\")\nprob = ODEProblem(fₘ, u₀, (0, 1e-4), constants_and_parameters)\nsoln = solve(prob, Vern7())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")\n\n@info(\"Solving\")\nprob = ODEProblem(fₘ, u₀, (0, tₑ), constants_and_parameters)\nsoln = solve(prob, Vern7())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"halmo/halmo/#Results","page":"Halfar-NS","title":"Results","text":"","category":"section"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"Let's look at the dynamics of the ice:","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"begin\n frames = 200\n fig = Figure()\n ax = LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.mesh!(ax, s, color=soln(0).ice_thickness, colormap=:jet, colorrange=extrema(soln(0).ice_thickness))\n\n Colorbar(fig[1,2], msh)\n record(fig, \"halmo_ice.gif\", range(0.0, tₑ; length=frames); framerate = 20) do t\n msh.color = soln(t).ice_thickness\n end\nend","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"(Image: HalfarMohamedIce)","category":"page"},{"location":"halmo/halmo/","page":"Halfar-NS","title":"Halfar-NS","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"ascii/#ASCII-and-Vector-Calculus-Operators","page":"ASCII Operators","title":"ASCII and Vector Calculus Operators","text":"","category":"section"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"include(joinpath(Base.@__DIR__, \"..\", \"docinfo.jl\"))\ninfo = DocInfo.Info()","category":"page"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"Some users may have trouble entering unicode characters like ⋆ or ∂ in their development environment. So, we offer the following ASCII equivalents. Further, some users may like to use vector calculus symbols instead of exterior calculus symbols where possible. We offer support for such symbols as well.","category":"page"},{"location":"ascii/#ASCII-Equivalents","page":"ASCII Operators","title":"ASCII Equivalents","text":"","category":"section"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"Unicode ASCII Meaning\n∂ₜ dt derivative w.r.t. time\n⋆ star Hodge star, generalizing transpose\nΔ lapl laplacian\n∧ wedge wedge product, generalizing the cross product\n⋆⁻¹ star_inv Hodge star, generalizing transpose\n∘(⋆,d,⋆) div divergence, a.k.a. ∇⋅\navg₀₁ avg_01 average values stored on endpoints of edges","category":"page"},{"location":"ascii/#Vector-Calculus-Equivalents","page":"ASCII Operators","title":"Vector Calculus Equivalents","text":"","category":"section"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"Vec DEC How to enter\ngrad d grad\ndiv ∘(⋆,d,⋆) div\ncurl ∘(d,⋆) curl\n∇ d \\nabla\n∇ᵈ ∘(⋆,d,⋆) \\nabla \\ \\^d \\\n∇x ∘(d,⋆) \\nabla \\ x\nadv(X,Y) ∘(⋆,d,⋆)(X∧Y) adv","category":"page"},{"location":"ascii/","page":"ASCII Operators","title":"ASCII Operators","text":"DocInfo.get_report(info) # hide","category":"page"},{"location":"#Decapodes.jl","page":"Decapodes.jl","title":"Decapodes.jl","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"Decapodes.jl is a framework for developing, composing, and simulating physical systems.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"Decapodes.jl is the synthesis of Applied Category Theory (ACT) techniques for formalizing and composing physics equations, and Discrete Exterior Calculus (DEC) techniques for formalizing differential operators. CombinatorialSpaces.jl hosts tools for discretizing space and defining DEC operators on simplicial complexes, and DiagrammaticEquations.jl hosts tooling for representing the equations as formal ACT diagrams. This repository combines these two packages, compiling diagrams down to runnable simulation code.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"By combining the power of ACT and the DEC, we seek to improve the scientific computing workflow. Decapodes simulations are hierarchically composable, generalize over any type of manifold, and are performant and accurate with a declarative domain specific language (DSL) that is human-readable.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"(Image: Grigoriev Ice Cap Dynamics)","category":"page"},{"location":"#Getting-started","page":"Decapodes.jl","title":"Getting started","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"Walkthroughs creating, composing, and solving Decapodes are available in the side-bar of this documentation page. Further example scripts are available in the examples folder of the Decapodes.jl GitHub repo, and will be added to this documentation site soon.","category":"page"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"note: Under Active Development\nThis library is currently under active development, and so is not yet at a point where a constant API/behavior can be assumed. That being said, if this project looks interesting/relevant please contact us and let us know!","category":"page"}] }