diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 2cd9c4b4..093e2a92 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.1","generation_timestamp":"2024-02-28T05:20:22","documenter_version":"1.2.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.1","generation_timestamp":"2024-02-28T14:27:06","documenter_version":"1.2.1"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index 6948e8fb..42314925 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -1,2 +1,2 @@ -Library Reference · Decapodes.jl

Library Reference

Decapodes

Decapodes.SpherePointType
SpherePoint{T}(p)

a point in spherical coordinates, intended as a wrapper around Point3 from GeometryBasics.

source
Decapodes.TangentBasisMethod
tb(w)

Take a linear combinations of the tangent vectors at the base point. Use this to get a vector tangent to the sphere in the coordinate system of the base point. If the base point is in spherical coordinates, this is the identity, if the base point is in cartesian coordinates, it returns the tangent vector in cartesian coordinates.

source
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
+Library Reference · Decapodes.jl

Library Reference

Decapodes

Decapodes.SpherePointType
SpherePoint{T}(p)

a point in spherical coordinates, intended as a wrapper around Point3 from GeometryBasics.

source
Decapodes.TangentBasisMethod
tb(w)

Take a linear combinations of the tangent vectors at the base point. Use this to get a vector tangent to the sphere in the coordinate system of the base point. If the base point is in spherical coordinates, this is the identity, if the base point is in cartesian coordinates, it returns the tangent vector in cartesian coordinates.

source
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
diff --git a/dev/ascii/index.html b/dev/ascii/index.html index 90d67d2e..f88cca2c 100644 --- a/dev/ascii/index.html +++ b/dev/ascii/index.html @@ -1,2 +1,2 @@ -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. ∇⋅

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
+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. ∇⋅

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
diff --git a/dev/bc_debug/index.html b/dev/bc_debug/index.html index f5b3e3c3..de3628d2 100644 --- a/dev/bc_debug/index.html +++ b/dev/bc_debug/index.html @@ -345,4 +345,4 @@ # 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"

+end
"diff_adv_right.gif"

diff --git a/dev/budyko_sellers_halfar/index.html b/dev/budyko_sellers_halfar/index.html index a3922ce5..6b73e040 100644 --- a/dev/budyko_sellers_halfar/index.html +++ b/dev/budyko_sellers_halfar/index.html @@ -253,4 +253,4 @@ record(fig, "budyko_sellers_halfar_h.gif", range(0.0, tₑ; length=frames); framerate = 15) do t lines!(fig[1,1], map(x -> x[1], point(s′)), soln(t).halfar_h) end -end
"budyko_sellers_halfar_h.gif"

BSH_Temperature

BSH_IceHeight

+end
"budyko_sellers_halfar_h.gif"

BSH_Temperature

BSH_IceHeight

diff --git a/dev/canon/index.html b/dev/canon/index.html index 93f4e7b2..47786d38 100644 --- a/dev/canon/index.html +++ b/dev/canon/index.html @@ -3,33 +3,33 @@ (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.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
+ϕ == k(d₀(C))
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

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

Mohamed Eq. 10, N2

Source

Model

𝐮::Form1
               
 (P, 𝑝ᵈ)::Form0
               
@@ -39,7 +39,7 @@
               
 𝑝ᵈ == P + half * i(𝐮, 𝐮)
               
-𝐮̇ == μ * (∘(d, ⋆, d, ⋆))(𝐮) + negone * (⋆₁⁻¹)(𝐮 ∧₁₀ₚᵈ (⋆)(d(𝐮))) + d(𝑝ᵈ)
source
Decapodes.Canon.Physics.momentumConstant

Momentum

Source

Model

(f, b)::Form0
+𝐮̇ == μ * (∘(d, ⋆, d, ⋆))(𝐮) + negone * (⋆₁⁻¹)(𝐮 ∧₁₀ₚᵈ (⋆)(d(𝐮))) + d(𝑝ᵈ)
source
Decapodes.Canon.Physics.momentumConstant

Momentum

Source

Model

(f, b)::Form0
               
 (v, V, g, Fᵥ, uˢ, v_up)::Form1
               
@@ -51,7 +51,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}
               
@@ -61,7 +61,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
               
@@ -69,7 +69,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
               
@@ -81,7 +81,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
               
@@ -99,13 +99,13 @@
               
 ρ_up == (∘(⋆, d, ⋆))(-1 * (ρ ∧₀₁ q))
               
-ρ̇ == ∂ρ(ρ_up)
source
Decapodes.Canon.Physics.schroedingerConstant

Schoedinger

Source

The evolution of the wave function over time.

Model

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

Schoedinger

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
               
@@ -113,7 +113,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
               
@@ -129,7 +129,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{X}
+∂ₜ(V) == V̇
source
Decapodes.Canon.Chemistry.brusselatorConstant

Brusselator

Source

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

Model

(U, V)::Form0{X}
               
 (U2V, One)::Form0{X}
               
@@ -147,33 +147,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
               
@@ -189,36 +189,36 @@
               
 Ṫ == ∂_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
+A == avg₀₁(5.8282 * 10 ^ (-0.236Tₛ) * 1.65e7)source diff --git a/dev/cism/index.html b/dev/cism/index.html index e1957314..e97b3885 100644 --- a/dev/cism/index.html +++ b/dev/cism/index.html @@ -208,14 +208,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())
-c = run(b)
BenchmarkTools.Trial: 265 samples with 1 evaluation.
- Range (minmax):  17.794 ms31.866 ms   GC (min … max): 0.00% … 23.46%
- Time  (median):     18.052 ms               GC (median):    0.00%
- Time  (mean ± σ):   18.736 ms ±  1.838 ms   GC (mean ± σ):  2.37% ±  4.34%
+c = run(b)
BenchmarkTools.Trial: 263 samples with 1 evaluation.
+ Range (minmax):  17.849 ms40.944 ms   GC (min … max): 0.00% … 0.00%
+ Time  (median):     18.008 ms               GC (median):    0.00%
+ Time  (mean ± σ):   18.851 ms ±  2.422 ms   GC (mean ± σ):  2.37% ± 4.39%
 
-  ▆      ▁▃▂                                               
-  ██▄▁▁▁▁▁▁███▇▄▄▄▄▁▁▁▁▁▄▁▄▁▁▁▁▁▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▁▁▄ ▅
-  17.8 ms      Histogram: log(frequency) by time      28.2 ms <
+  █         ▂▁                                                
+  █▆▆▄▁▁▁▆███▁▆▆▁▄▄▁▁▄▁▁▄▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▁▄▁▁▁▁▁▁▅▄▄ ▅
+  17.8 ms      Histogram: log(frequency) by time      28.7 ms <
 
  Memory estimate: 26.46 MiB, allocs estimate: 2114.
# 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
@@ -278,4 +278,4 @@
   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 during a hackathon.

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.

+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 during a hackathon.

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.

diff --git a/dev/equations/index.html b/dev/equations/index.html index 5cb6271c..01f47e44 100644 --- a/dev/equations/index.html +++ b/dev/equations/index.html @@ -970,4 +970,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.

+'/>

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.

diff --git a/dev/grigoriev/index.html b/dev/grigoriev/index.html index 75e0d162..d8b99219 100644 --- a/dev/grigoriev/index.html +++ b/dev/grigoriev/index.html @@ -193,4 +193,4 @@ time[] = t end end -save_dynamics("grigoriev.gif")
"grigoriev.gif"

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

+save_dynamics("grigoriev.gif")
"grigoriev.gif"

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

diff --git a/dev/ice_dynamics/index.html b/dev/ice_dynamics/index.html index 87697bd9..4bcd0181 100644 --- a/dev/ice_dynamics/index.html +++ b/dev/ice_dynamics/index.html @@ -351,4 +351,4 @@ 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

+end
"ice_dynamics2D_sphere.gif"

IceDynamics2DSphere

diff --git a/dev/index.html b/dev/index.html index 53ac7a90..218906ed 100644 --- a/dev/index.html +++ b/dev/index.html @@ -1,2 +1,2 @@ -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 simulatable 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

NOTE

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!

Getting started

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

NOTE

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 simulatable 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

NOTE

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!

Getting started

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

NOTE

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/index.html b/dev/klausmeier/index.html index 7c0c38b9..e9e3a3dc 100644 --- a/dev/klausmeier/index.html +++ b/dev/klausmeier/index.html @@ -365,4 +365,4 @@ time[] = t end end -save_dynamics(:N, 20, "klausmeier.gif")
"klausmeier.gif"

Klausmeier

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

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.

+save_dynamics(:N, 20, "klausmeier.gif")
"klausmeier.gif"

Klausmeier

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

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.

diff --git a/dev/nhs/169a1378.svg b/dev/nhs/169a1378.svg new file mode 100644 index 00000000..c5324b62 --- /dev/null +++ b/dev/nhs/169a1378.svg @@ -0,0 +1,213 @@ + + + + + + +G + + + +n1 + +b:Ω̃₀ + + + +n2 + +T:Ω̃₀ + + + +n12 + +ΩC×Ω̃₀ + + + +n2->n12 + + +π₂ + + + +n3 + +S:Ω̃₀ + + + +n13 + +ΩC×Ω̃₀ + + + +n3->n13 + + +π₂ + + + +n4 + +g:ΩC + + + +n15 + +ΩC×Ω• + + + +n4->n15 + + +π₁ + + + +n5 + +α:ΩC + + + +n5->n12 + + +π₁ + + + +n6 + +β:ΩC + + + +n6->n13 + + +π₁ + + + +n7 + +•1:Ω• + + + +n14 + +Ω•×Ω• + + + +n7->n14 + + +π₂ + + + +n8 + +•2:Ω• + + + +n8->n15 + + +π₂ + + + +n9 + +•3:Ω• + + + +n9->n14 + + +π₁ + + + +n10 + + + +n10->n2 + + + + + +n10->n3 + + + + + +n10->n4 + + + + + +n10->n5 + + + + + +n10->n6 + + + + + +n11 + + + +n12->n9 + + +* + + + +n13->n7 + + +* + + + +n14->n8 + + +- + + + +n15->n1 + + +* + + + diff --git a/dev/nhs/3dc4f7a9.svg b/dev/nhs/3dc4f7a9.svg new file mode 100644 index 00000000..a34db2d2 --- /dev/null +++ b/dev/nhs/3dc4f7a9.svg @@ -0,0 +1,517 @@ + + + + + + +G + + + +n1 + +v:Ω̃₁ + + + +n8 + +turbulence_•2:Ω• + + + +n1->n8 + + +Δᵈ₁ + + + +n28 + +Ω̃₁×Ω• + + + +n1->n28 + + +π₁ + + + +n32 + +Ω̃₁×Ω• + + + +n1->n32 + + +π₁ + + + +n2 + +c:Ω̃₀ + + + +n7 + +turbulence_•1:Ω• + + + +n2->n7 + + +Δᵈ₀ + + + +n12 + +continuity_ċ:Ω• + + + +n2->n12 + + +∂ₜ + + + +n19 + +continuity_•7:Ω• + + + +n2->n19 + + +d + + + +n21 + +continuity_•9:Ω• + + + +n2->n21 + + +d + + + +n3 + +turbulence_StressDivergence:Ω̃₁ + + + +n4 + +FD:Ω̃₀ + + + +n34 + +Ω•×Ω̃₀ + + + +n4->n34 + + +π₂ + + + +n5 + +turbulence_κ:ΩC + + + +n27 + +ΩC×Ω• + + + +n5->n27 + + +π₁ + + + +n6 + +turbulence_nu:ΩC + + + +n26 + +ΩC×Ω• + + + +n6->n26 + + +π₁ + + + +n7->n27 + + +π₂ + + + +n8->n26 + + +π₂ + + + +n9 + +continuity_C:Ω̃₀ + + + +n23 + +continuity_•11:Ω• + + + +n9->n23 + + +d + + + +n10 + +continuity_F:Ω̃₀ + + + +n35 + +Σ1 + + + +n10->n35 + + + + + +n11 + +continuity_V:Ω̃₁ + + + +n30 + +Ω̃₁×Ω• + + + +n11->n30 + + +π₁ + + + +n25 + + + +n12->n25 + + + + + +n13 + +continuity_•2:Ω• + + + +n13->n35 + + + + + +n14 + +continuity_•3:Ω• + + + +n14->n34 + + +π₁ + + + +n15 + +continuity_•4:Ω• + + + +n33 + +Ω•×Ω• + + + +n15->n33 + + +π₁ + + + +n16 + +continuity_•5:Ω• + + + +n31 + +Ω•×Ω• + + + +n16->n31 + + +π₁ + + + +n17 + +-1:ΩL + + + +n29 + +ΩL×Ω• + + + +n17->n29 + + +π₁ + + + +n18 + +continuity_•6:Ω• + + + +n18->n29 + + +π₂ + + + +n19->n28 + + +π₂ + + + +n20 + +continuity_•8:Ω• + + + +n20->n31 + + +π₂ + + + +n21->n30 + + +π₂ + + + +n22 + +continuity_•10:Ω• + + + +n22->n33 + + +π₂ + + + +n23->n32 + + +π₂ + + + +n24 + + + +n24->n1 + + + + + +n24->n2 + + + + + +n24->n5 + + + + + +n24->n6 + + + + + +n24->n9 + + + + + +n24->n10 + + + + + +n24->n11 + + + + + +n26->n3 + + +* + + + +n27->n4 + + +* + + + +n28->n18 + + +ι₁₁ + + + +n29->n16 + + +* + + + +n30->n20 + + +ι₁₁ + + + +n31->n15 + + +- + + + +n32->n22 + + +ι₁₁ + + + +n33->n14 + + +- + + + +n34->n13 + + +- + + + +n35->n12 + + ++ + + + diff --git a/dev/nhs/5e1f7338.svg b/dev/nhs/5e1f7338.svg new file mode 100644 index 00000000..9e6cd81c --- /dev/null +++ b/dev/nhs/5e1f7338.svg @@ -0,0 +1,816 @@ + + + + + + +G + + + +n1 + +v:Ω̃₁ + + + +n11 + +v̇:Ω• + + + +n1->n11 + + +∂ₜ + + + +n28 + +•16:Ω• + + + +n1->n28 + + +d + + + +n38 + +Ω̃₁×Ω̃₁ + + + +n1->n38 + + +π₁ + + + +n1->n38 + + +π₂ + + + +n39 + +Ω̃₁×Ω̃₁ + + + +n1->n39 + + +π₁ + + + +n1->n39 + + +π₂ + + + +n41 + +Ω̃₁×Ω̃₁ + + + +n1->n41 + + +π₁ + + + +n43 + +Ω̃₁×Ω• + + + +n1->n43 + + +π₁ + + + +n46 + +Ω•×Ω̃₁ + + + +n1->n46 + + +π₂ + + + +n2 + +V:Ω̃₁ + + + +n26 + +•14:Ω• + + + +n2->n26 + + +d + + + +n2->n41 + + +π₂ + + + +n44 + +Ω̃₁×Ω• + + + +n2->n44 + + +π₁ + + + +n3 + +f:Ω₀ + + + +n45 + +Ω₀×Ω• + + + +n3->n45 + + +π₁ + + + +n4 + +uˢ:Ω̃₁ + + + +n32 + +•19:Ω• + + + +n4->n32 + + +d⋅⋆ + + + +n5 + +∂tuˢ:Ω̃₁ + + + +n54 + +Σ4 + + + +n5->n54 + + + + + +n6 + +p:Ω̃₀ + + + +n33 + +•20:Ω• + + + +n6->n33 + + +d + + + +n7 + +b:Ω̃₀ + + + +n49 + +Ω̃₀×Ω̃₁ + + + +n7->n49 + + +π₁ + + + +n8 + +ĝ:Ω̃₁ + + + +n8->n49 + + +π₂ + + + +n9 + +Fᵥ:Ω̃₁ + + + +n9->n54 + + + + + +n10 + +StressDivergence:Ω̃₁ + + + +n50 + +Ω•×Ω̃₁ + + + +n10->n50 + + +π₂ + + + +n37 + + + +n11->n37 + + + + + +n12 + +•2:Ω• + + + +n12->n54 + + + + + +n13 + +•3:Ω• + + + +n53 + +Σ3 + + + +n13->n53 + + + + + +n14 + +•4:Ω• + + + +n48 + +Ω•×Ω• + + + +n14->n48 + + +π₁ + + + +n15 + +•5:Ω• + + + +n52 + +Σ2 + + + +n15->n52 + + + + + +n16 + +•6:Ω• + + + +n51 + +Σ1 + + + +n16->n51 + + + + + +n17 + +•7:Ω• + + + +n17->n16 + + +- + + + +n18 + +•8:Ω• + + + +n18->n51 + + + + + +n19 + +0.5:ΩL + + + +n40 + +ΩL×Ω• + + + +n19->n40 + + +π₁ + + + +n20 + +•9:Ω• + + + +n20->n40 + + +π₂ + + + +n21 + +•10:Ω• + + + +n21->n20 + + +d + + + +n22 + +sum_1:Ω• + + + +n42 + +Ω•×Ω• + + + +n22->n42 + + +π₁ + + + +n23 + +•11:Ω• + + + +n23->n42 + + +π₂ + + + +n24 + +•12:Ω• + + + +n24->n23 + + +d + + + +n25 + +•13:Ω• + + + +n25->n52 + + + + + +n26->n43 + + +π₂ + + + +n27 + +•15:Ω• + + + +n27->n52 + + + + + +n28->n44 + + +π₂ + + + +n29 + +sum_2:Ω• + + + +n47 + +Ω•×Ω• + + + +n29->n47 + + +π₁ + + + +n30 + +•17:Ω• + + + +n30->n47 + + +π₂ + + + +n31 + +•18:Ω• + + + +n31->n46 + + +π₁ + + + +n32->n45 + + +π₂ + + + +n33->n48 + + +π₂ + + + +n34 + +•21:Ω• + + + +n34->n53 + + + + + +n35 + +sum_3:Ω• + + + +n35->n50 + + +π₁ + + + +n36 + + + +n36->n1 + + + + + +n36->n2 + + + + + +n36->n3 + + + + + +n36->n4 + + + + + +n36->n5 + + + + + +n36->n6 + + + + + +n36->n7 + + + + + +n36->n8 + + + + + +n36->n9 + + + + + +n36->n10 + + + + + +n38->n17 + + +ℒ₁ + + + +n39->n21 + + +ι₁₁ + + + +n40->n18 + + +* + + + +n41->n24 + + +ι₁₁ + + + +n42->n15 + + +- + + + +n43->n25 + + +ι₁₂ + + + +n44->n27 + + +ι₁₂ + + + +n45->n31 + + +- + + + +n46->n30 + + +∧ᵖᵈ₀₁ + + + +n47->n14 + + +- + + + +n48->n13 + + +- + + + +n49->n34 + + +∧ᵈᵈ₀₁ + + + +n50->n12 + + +- + + + +n51->n22 + + ++ + + + +n52->n29 + + ++ + + + +n53->n35 + + ++ + + + +n54->n11 + + ++ + + + diff --git a/dev/nhs/aeb99c8a.svg b/dev/nhs/aeb99c8a.svg new file mode 100644 index 00000000..26aedaf7 --- /dev/null +++ b/dev/nhs/aeb99c8a.svg @@ -0,0 +1,1886 @@ + + + + + + +G + + + +n1 + +v:Ω̃₁ + + + +n11 + +momentum_v̇:Ω• + + + +n1->n11 + + +∂ₜ + + + +n28 + +momentum_•16:Ω• + + + +n1->n28 + + +d + + + +n41 + +temperature_turbulence_•2:Ω• + + + +n1->n41 + + +Δᵈ₁ + + + +n60 + +salinity_turbulence_•2:Ω• + + + +n1->n60 + + +Δᵈ₁ + + + +n83 + +Ω̃₁×Ω̃₁ + + + +n1->n83 + + +π₁ + + + +n1->n83 + + +π₂ + + + +n84 + +Ω̃₁×Ω̃₁ + + + +n1->n84 + + +π₁ + + + +n1->n84 + + +π₂ + + + +n86 + +Ω̃₁×Ω̃₁ + + + +n1->n86 + + +π₁ + + + +n88 + +Ω̃₁×Ω• + + + +n1->n88 + + +π₁ + + + +n91 + +Ω•×Ω̃₁ + + + +n1->n91 + + +π₂ + + + +n98 + +Ω̃₁×Ω• + + + +n1->n98 + + +π₁ + + + +n102 + +Ω̃₁×Ω• + + + +n1->n102 + + +π₁ + + + +n107 + +Ω̃₁×Ω• + + + +n1->n107 + + +π₁ + + + +n111 + +Ω̃₁×Ω• + + + +n1->n111 + + +π₁ + + + +n2 + +V:Ω̃₁ + + + +n26 + +momentum_•14:Ω• + + + +n2->n26 + + +d + + + +n2->n86 + + +π₂ + + + +n89 + +Ω̃₁×Ω• + + + +n2->n89 + + +π₁ + + + +n100 + +Ω̃₁×Ω• + + + +n2->n100 + + +π₁ + + + +n109 + +Ω̃₁×Ω• + + + +n2->n109 + + +π₁ + + + +n3 + +momentum_f:Ω₀ + + + +n90 + +Ω₀×Ω• + + + +n3->n90 + + +π₁ + + + +n4 + +momentum_uˢ:Ω̃₁ + + + +n32 + +momentum_•19:Ω• + + + +n4->n32 + + +d⋅⋆ + + + +n5 + +momentum_∂tuˢ:Ω̃₁ + + + +n121 + +Σ4 + + + +n5->n121 + + + + + +n6 + +momentum_p:Ω̃₀ + + + +n33 + +momentum_•20:Ω• + + + +n6->n33 + + +d + + + +n7 + +b:Ω̃₀ + + + +n94 + +Ω̃₀×Ω̃₁ + + + +n7->n94 + + +π₁ + + + +n8 + +momentum_ĝ:Ω̃₁ + + + +n8->n94 + + +π₂ + + + +n9 + +momentum_Fᵥ:Ω̃₁ + + + +n9->n121 + + + + + +n10 + +SD:Ω̃₁ + + + +n95 + +Ω•×Ω̃₁ + + + +n10->n95 + + +π₂ + + + +n82 + + + +n11->n82 + + + + + +n12 + +momentum_•2:Ω• + + + +n12->n121 + + + + + +n13 + +momentum_•3:Ω• + + + +n120 + +Σ3 + + + +n13->n120 + + + + + +n14 + +momentum_•4:Ω• + + + +n93 + +Ω•×Ω• + + + +n14->n93 + + +π₁ + + + +n15 + +momentum_•5:Ω• + + + +n119 + +Σ2 + + + +n15->n119 + + + + + +n16 + +momentum_•6:Ω• + + + +n118 + +Σ1 + + + +n16->n118 + + + + + +n17 + +momentum_•7:Ω• + + + +n17->n16 + + +- + + + +n18 + +momentum_•8:Ω• + + + +n18->n118 + + + + + +n19 + +0.5:ΩL + + + +n85 + +ΩL×Ω• + + + +n19->n85 + + +π₁ + + + +n20 + +momentum_•9:Ω• + + + +n20->n85 + + +π₂ + + + +n21 + +momentum_•10:Ω• + + + +n21->n20 + + +d + + + +n22 + +momentum_sum_1:Ω• + + + +n87 + +Ω•×Ω• + + + +n22->n87 + + +π₁ + + + +n23 + +momentum_•11:Ω• + + + +n23->n87 + + +π₂ + + + +n24 + +momentum_•12:Ω• + + + +n24->n23 + + +d + + + +n25 + +momentum_•13:Ω• + + + +n25->n119 + + + + + +n26->n88 + + +π₂ + + + +n27 + +momentum_•15:Ω• + + + +n27->n119 + + + + + +n28->n89 + + +π₂ + + + +n29 + +momentum_sum_2:Ω• + + + +n92 + +Ω•×Ω• + + + +n29->n92 + + +π₁ + + + +n30 + +momentum_•17:Ω• + + + +n30->n92 + + +π₂ + + + +n31 + +momentum_•18:Ω• + + + +n31->n91 + + +π₁ + + + +n32->n90 + + +π₂ + + + +n33->n93 + + +π₂ + + + +n34 + +momentum_•21:Ω• + + + +n34->n120 + + + + + +n35 + +momentum_sum_3:Ω• + + + +n35->n95 + + +π₁ + + + +n36 + +T:Ω̃₀ + + + +n40 + +temperature_turbulence_•1:Ω• + + + +n36->n40 + + +Δᵈ₀ + + + +n44 + +temperature_continuity_ċ:Ω• + + + +n36->n44 + + +∂ₜ + + + +n51 + +temperature_continuity_•7:Ω• + + + +n36->n51 + + +d + + + +n53 + +temperature_continuity_•9:Ω• + + + +n36->n53 + + +d + + + +n114 + +ΩC×Ω̃₀ + + + +n36->n114 + + +π₂ + + + +n37 + +temperature_FD:Ω̃₀ + + + +n104 + +Ω•×Ω̃₀ + + + +n37->n104 + + +π₂ + + + +n38 + +temperature_turbulence_κ:ΩC + + + +n97 + +ΩC×Ω• + + + +n38->n97 + + +π₁ + + + +n39 + +nu:ΩC + + + +n96 + +ΩC×Ω• + + + +n39->n96 + + +π₁ + + + +n105 + +ΩC×Ω• + + + +n39->n105 + + +π₁ + + + +n40->n97 + + +π₂ + + + +n41->n96 + + +π₂ + + + +n42 + +temperature_continuity_C:Ω̃₀ + + + +n55 + +temperature_continuity_•11:Ω• + + + +n42->n55 + + +d + + + +n43 + +temperature_continuity_F:Ω̃₀ + + + +n122 + +Σ5 + + + +n43->n122 + + + + + +n44->n82 + + + + + +n45 + +temperature_continuity_•2:Ω• + + + +n45->n122 + + + + + +n46 + +temperature_continuity_•3:Ω• + + + +n46->n104 + + +π₁ + + + +n47 + +temperature_continuity_•4:Ω• + + + +n103 + +Ω•×Ω• + + + +n47->n103 + + +π₁ + + + +n48 + +temperature_continuity_•5:Ω• + + + +n101 + +Ω•×Ω• + + + +n48->n101 + + +π₁ + + + +n49 + +-1:ΩL + + + +n99 + +ΩL×Ω• + + + +n49->n99 + + +π₁ + + + +n50 + +temperature_continuity_•6:Ω• + + + +n50->n99 + + +π₂ + + + +n51->n98 + + +π₂ + + + +n52 + +temperature_continuity_•8:Ω• + + + +n52->n101 + + +π₂ + + + +n53->n100 + + +π₂ + + + +n54 + +temperature_continuity_•10:Ω• + + + +n54->n103 + + +π₂ + + + +n55->n102 + + +π₂ + + + +n56 + +S:Ω̃₀ + + + +n59 + +salinity_turbulence_•1:Ω• + + + +n56->n59 + + +Δᵈ₀ + + + +n63 + +salinity_continuity_ċ:Ω• + + + +n56->n63 + + +∂ₜ + + + +n70 + +salinity_continuity_•7:Ω• + + + +n56->n70 + + +d + + + +n72 + +salinity_continuity_•9:Ω• + + + +n56->n72 + + +d + + + +n115 + +ΩC×Ω̃₀ + + + +n56->n115 + + +π₂ + + + +n57 + +salinity_FD:Ω̃₀ + + + +n113 + +Ω•×Ω̃₀ + + + +n57->n113 + + +π₂ + + + +n58 + +salinity_turbulence_κ:ΩC + + + +n106 + +ΩC×Ω• + + + +n58->n106 + + +π₁ + + + +n59->n106 + + +π₂ + + + +n60->n105 + + +π₂ + + + +n61 + +salinity_continuity_C:Ω̃₀ + + + +n74 + +salinity_continuity_•11:Ω• + + + +n61->n74 + + +d + + + +n62 + +salinity_continuity_F:Ω̃₀ + + + +n123 + +Σ6 + + + +n62->n123 + + + + + +n63->n82 + + + + + +n64 + +salinity_continuity_•2:Ω• + + + +n64->n123 + + + + + +n65 + +salinity_continuity_•3:Ω• + + + +n65->n113 + + +π₁ + + + +n66 + +salinity_continuity_•4:Ω• + + + +n112 + +Ω•×Ω• + + + +n66->n112 + + +π₁ + + + +n67 + +salinity_continuity_•5:Ω• + + + +n110 + +Ω•×Ω• + + + +n67->n110 + + +π₁ + + + +n68 + +-1:ΩL + + + +n108 + +ΩL×Ω• + + + +n68->n108 + + +π₁ + + + +n69 + +salinity_continuity_•6:Ω• + + + +n69->n108 + + +π₂ + + + +n70->n107 + + +π₂ + + + +n71 + +salinity_continuity_•8:Ω• + + + +n71->n110 + + +π₂ + + + +n72->n109 + + +π₂ + + + +n73 + +salinity_continuity_•10:Ω• + + + +n73->n112 + + +π₂ + + + +n74->n111 + + +π₂ + + + +n75 + +eos_g:ΩC + + + +n117 + +ΩC×Ω• + + + +n75->n117 + + +π₁ + + + +n76 + +eos_α:ΩC + + + +n76->n114 + + +π₁ + + + +n77 + +eos_β:ΩC + + + +n77->n115 + + +π₁ + + + +n78 + +eos_•1:Ω• + + + +n116 + +Ω•×Ω• + + + +n78->n116 + + +π₂ + + + +n79 + +eos_•2:Ω• + + + +n79->n117 + + +π₂ + + + +n80 + +eos_•3:Ω• + + + +n80->n116 + + +π₁ + + + +n81 + + + +n81->n1 + + + + + +n81->n2 + + + + + +n81->n3 + + + + + +n81->n4 + + + + + +n81->n5 + + + + + +n81->n6 + + + + + +n81->n8 + + + + + +n81->n9 + + + + + +n81->n36 + + + + + +n81->n38 + + + + + +n81->n39 + + + + + +n81->n42 + + + + + +n81->n43 + + + + + +n81->n56 + + + + + +n81->n58 + + + + + +n81->n61 + + + + + +n81->n62 + + + + + +n81->n75 + + + + + +n81->n76 + + + + + +n81->n77 + + + + + +n83->n17 + + +ℒ₁ + + + +n84->n21 + + +ι₁₁ + + + +n85->n18 + + +* + + + +n86->n24 + + +ι₁₁ + + + +n87->n15 + + +- + + + +n88->n25 + + +ι₁₂ + + + +n89->n27 + + +ι₁₂ + + + +n90->n31 + + +- + + + +n91->n30 + + +∧ᵖᵈ₀₁ + + + +n92->n14 + + +- + + + +n93->n13 + + +- + + + +n94->n34 + + +∧ᵈᵈ₀₁ + + + +n95->n12 + + +- + + + +n96->n10 + + +* + + + +n97->n37 + + +* + + + +n98->n50 + + +ι₁₁ + + + +n99->n48 + + +* + + + +n100->n52 + + +ι₁₁ + + + +n101->n47 + + +- + + + +n102->n54 + + +ι₁₁ + + + +n103->n46 + + +- + + + +n104->n45 + + +- + + + +n105->n10 + + +* + + + +n106->n57 + + +* + + + +n107->n69 + + +ι₁₁ + + + +n108->n67 + + +* + + + +n109->n71 + + +ι₁₁ + + + +n110->n66 + + +- + + + +n111->n73 + + +ι₁₁ + + + +n112->n65 + + +- + + + +n113->n64 + + +- + + + +n114->n80 + + +* + + + +n115->n78 + + +* + + + +n116->n79 + + +- + + + +n117->n7 + + +* + + + +n118->n22 + + ++ + + + +n119->n29 + + ++ + + + +n120->n35 + + ++ + + + +n121->n11 + + ++ + + + +n122->n44 + + ++ + + + +n123->n63 + + ++ + + + diff --git a/dev/nhs/fa7acbc7.svg b/dev/nhs/fa7acbc7.svg new file mode 100644 index 00000000..596e943d --- /dev/null +++ b/dev/nhs/fa7acbc7.svg @@ -0,0 +1,413 @@ + + + + + + +G + + + +n1 + +c:Ω̃₀ + + + +n7 + +ċ:Ω• + + + +n1->n7 + + +∂ₜ + + + +n14 + +•7:Ω• + + + +n1->n14 + + +d + + + +n16 + +•9:Ω• + + + +n1->n16 + + +d + + + +n2 + +C:Ω̃₀ + + + +n18 + +•11:Ω• + + + +n2->n18 + + +d + + + +n3 + +F:Ω̃₀ + + + +n28 + +Σ1 + + + +n3->n28 + + + + + +n4 + +FluxDivergence:Ω̃₀ + + + +n27 + +Ω•×Ω̃₀ + + + +n4->n27 + + +π₂ + + + +n5 + +v:Ω̃₁ + + + +n21 + +Ω̃₁×Ω• + + + +n5->n21 + + +π₁ + + + +n25 + +Ω̃₁×Ω• + + + +n5->n25 + + +π₁ + + + +n6 + +V:Ω̃₁ + + + +n23 + +Ω̃₁×Ω• + + + +n6->n23 + + +π₁ + + + +n20 + + + +n7->n20 + + + + + +n8 + +•2:Ω• + + + +n8->n28 + + + + + +n9 + +•3:Ω• + + + +n9->n27 + + +π₁ + + + +n10 + +•4:Ω• + + + +n26 + +Ω•×Ω• + + + +n10->n26 + + +π₁ + + + +n11 + +•5:Ω• + + + +n24 + +Ω•×Ω• + + + +n11->n24 + + +π₁ + + + +n12 + +-1:ΩL + + + +n22 + +ΩL×Ω• + + + +n12->n22 + + +π₁ + + + +n13 + +•6:Ω• + + + +n13->n22 + + +π₂ + + + +n14->n21 + + +π₂ + + + +n15 + +•8:Ω• + + + +n15->n24 + + +π₂ + + + +n16->n23 + + +π₂ + + + +n17 + +•10:Ω• + + + +n17->n26 + + +π₂ + + + +n18->n25 + + +π₂ + + + +n19 + + + +n19->n1 + + + + + +n19->n2 + + + + + +n19->n3 + + + + + +n19->n4 + + + + + +n19->n5 + + + + + +n19->n6 + + + + + +n21->n13 + + +ι₁₁ + + + +n22->n11 + + +* + + + +n23->n15 + + +ι₁₁ + + + +n24->n10 + + +- + + + +n25->n17 + + +ι₁₁ + + + +n26->n9 + + +- + + + +n27->n8 + + +- + + + +n28->n7 + + ++ + + + diff --git a/dev/nhs/index.html b/dev/nhs/index.html index 3a10c29b..2e98a79d 100644 --- a/dev/nhs/index.html +++ b/dev/nhs/index.html @@ -33,461 +33,8 @@ StressDivergence + ∂tuˢ + Fᵥ -end
-DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:35, TVar:1, Op1:8, Op2:13, Σ:4, Summand:10, Type:0, Operator:0, Name:0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vartypename
1DualForm1v
2DualForm1V
3Form0f
4DualForm1
5DualForm1∂tuˢ
6DualForm0p
7DualForm0b
8DualForm1ĝ
9DualForm1Fᵥ
10DualForm1StressDivergence
11infer
12infer•2
13infer•3
14infer•4
15infer•5
16infer•6
17infer•7
18infer•8
19Literal0.5
20infer•9
21infer•10
22infersum_1
23infer•11
24infer•12
25infer•13
26infer•14
27infer•15
28infer•16
29infersum_2
30infer•17
31infer•18
32infer•19
33infer•20
34infer•21
35infersum_3
- - - - - - - - - - - - - -
TVarincl
111
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op1srctgtop1
1111∂ₜ
21716-
32120d
42423d
5226d
6128d
7432[:d, :⋆]
8633d
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op2proj1proj2resop2
11117ℒ₁
21121ι₁₁
3192018*
41224ι₁₁
5222315-
612625ι₁₂
722827ι₁₂
833231-
931130∧ᵖᵈ₀₁
10293014-
11143313-
127834∧ᵈᵈ₀₁
13351012-
- - - - - - - - - - - - - - - - - - - - - - - - - -
Σsum
122
229
335
411
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Summandsummandsummation
1161
2181
3152
4252
5272
6133
7343
8124
954
1094
-
-

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: ∇⋅τ = nuΔ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.

This is Equation 2: "The tracer conservation equation".

tracer_conservation = @decapode begin
+end
+to_graphviz(momentum)
Example block output

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: ∇⋅τ = nuΔ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.

This is Equation 2: "The tracer conservation equation".

tracer_conservation = @decapode begin
   (c,C,F,FluxDivergence)::DualForm0
   (v,V)::DualForm1
 
@@ -497,363 +44,14 @@
     ι₁₁(v,d(C)) -
     FluxDivergence +
     F
-end
-DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:18, TVar:1, Op1:4, Op2:7, Σ:1, Summand:2, Type:0, Operator:0, Name:0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vartypename
1DualForm0c
2DualForm0C
3DualForm0F
4DualForm0FluxDivergence
5DualForm1v
6DualForm1V
7inferċ
8infer•2
9infer•3
10infer•4
11infer•5
12Literal-1
13infer•6
14infer•7
15infer•8
16infer•9
17infer•10
18infer•11
- - - - - - - - - - - - - -
TVarincl
17
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op1srctgtop1
117∂ₜ
2114d
3116d
4218d
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op2proj1proj2resop2
151413ι₁₁
2121311*
361615ι₁₁
4111510-
551817ι₁₁
610179-
7948-
- - - - - - - - - - - - - -
Σsum
17
- - - - - - - - - - - - - - - - - - - - -
Summandsummandsummation
181
231
-
-

This is Equation 2: "Linear equation of state" of seawater buoyancy.

equation_of_state = @decapode begin
+end
+to_graphviz(tracer_conservation)
Example block output

This is Equation 2: "Linear equation of state" of seawater buoyancy.

equation_of_state = @decapode begin
   (b,T,S)::DualForm0
   (g,α,β)::Constant
 
   b == g*(α*T - β*S)
-end
-DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:9, TVar:0, Op1:0, Op2:4, Σ:0, Summand:0, Type:0, Operator:0, Name:0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vartypename
1DualForm0b
2DualForm0T
3DualForm0S
4Constantg
5Constantα
6Constantβ
7infer•1
8infer•2
9infer•3
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op2proj1proj2resop2
1529*
2637*
3978-
4481*
-
-

This is Equation 2: "Constant isotropic diffusivity".

isotropic_diffusivity = @decapode begin
+end
+to_graphviz(equation_of_state)
Example block output

This is Equation 2: "Constant isotropic diffusivity".

isotropic_diffusivity = @decapode begin
   v::DualForm1
   c::DualForm0
   StressDivergence::DualForm1
@@ -862,505 +60,249 @@
 
   StressDivergence == nu*Δᵈ₁(v)
   FluxDivergence == κ*Δᵈ₀(c)
-end
-DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:8, TVar:0, Op1:2, Op2:2, Σ:0, Summand:0, Type:0, Operator:0, Name:0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vartypename
1DualForm1v
2DualForm0c
3DualForm1StressDivergence
4DualForm0FluxDivergence
5Constantκ
6Constantnu
7infer•1
8infer•2
- - - - - - - - - - - - - - - - - - - - - - - -
Op1srctgtop1
118Δᵈ₁
227Δᵈ₀
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Op2proj1proj2resop2
1683*
2574*
-
-

Compatibility Guarantees via Operadic Composition

Decapodes composition is formally known as an "operad algebra". That means that we don't have to encode our composition in a single 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.

Besides it being convenient to break apart large UWDs into component UWDs, this hierarchical composition can enforce rules on our physical quantities.

For example:

  1. We want all the tracers (salinity, temperature, etc.) in our physics to obey the same conservation equation.
  2. We want them to obey the same "turbulence closure", which affects their flux-divergence term.
  3. At 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.

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.

Here, we specify the equations that any tracer obeys:

tracer_composition = @relation () begin
+end
+to_graphviz(isotropic_diffusivity)

Compatibility Guarantees via Operadic Composition

Decapodes composition is formally known as an "operad algebra". That means that we don't have to encode our composition in a single 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.

Besides it being convenient to break apart large UWDs into component UWDs, this hierarchical composition can enforce rules on our physical quantities.

For example:

  1. We want all the tracers (salinity, temperature, etc.) in our physics to obey the same conservation equation.
  2. We want them to obey the same "turbulence closure", which affects their flux-divergence term.
  3. At 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.

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.

Here, we specify the equations that any tracer obeys:

tracer_composition = @relation () begin
   # "The turbulence closure selected by the user determines the form of ... diffusive flux divergence"
   turbulence(FD,v,c)
 
   continuity(FD,v,c)
-end
-Catlab.Programs.RelationalPrograms.UntypedUnnamedRelationDiagram{Symbol, Symbol} {Box:2, Port:6, OuterPort:0, Junction:3, Name:0, VarName:0} - - - - - - - - - - - - - - - - - -
Boxname
1turbulence
2continuity
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Portboxjunction
111
212
313
421
522
623
- - - - - - - - - - - - - - - - - - - - - -
Junctionvariable
1FD
2v
3c
-
-

Let's "lock in" isotropic diffusivity by doing an intermediate oapply.

isotropic_tracer = apex(oapply(tracer_composition, [
+end
+to_graphviz(tracer_composition)

Let's "lock in" isotropic diffusivity by doing an intermediate oapply.

isotropic_tracer = apex(oapply(tracer_composition, [
   Open(isotropic_diffusivity, [:FluxDivergence, :v, :c]),
-  Open(tracer_conservation,   [:FluxDivergence, :v, :c])]))
-DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:23, TVar:1, Op1:6, Op2:9, Σ:1, Summand:2, Type:0, Operator:0, Name:0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vartypename
1DualForm1v
2DualForm0c
3DualForm1turbulence_StressDivergence
4DualForm0FD
5Constantturbulence_κ
6Constantturbulence_nu
7inferturbulence_•1
8inferturbulence_•2
9DualForm0continuity_C
10DualForm0continuity_F
11DualForm1continuity_V
12infercontinuity_ċ
13infercontinuity_•2
14infercontinuity_•3
15infercontinuity_•4
16infercontinuity_•5
17Literal-1
18infercontinuity_•6
19infercontinuity_•7
20infercontinuity_•8
21infercontinuity_•9
22infercontinuity_•10
23infercontinuity_•11
- - - - - - - - - - - - - -
TVarincl
112
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op1srctgtop1
118Δᵈ₁
227Δᵈ₀
3212∂ₜ
4219d
5221d
6923d
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op2proj1proj2resop2
1683*
2574*
311918ι₁₁
4171816*
5112120ι₁₁
6162015-
712322ι₁₁
8152214-
914413-
- - - - - - - - - - - - - -
Σsum
112
- - - - - - - - - - - - - - - - - - - - -
Summandsummandsummation
1131
2101
-
-

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.

nonhydrostatic_composition = @relation () begin
+  Open(tracer_conservation,   [:FluxDivergence, :v, :c])]))
+to_graphviz(isotropic_tracer)
Example block output

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.

nonhydrostatic_composition = @relation () begin
   # "The turbulence closure selected by the user determines the form of stress divergence"
   #   => Note that the StressDivergence term, SD, is shared by momentum and all the tracers.
   momentum(V, v, b, SD)
@@ -1374,953 +316,172 @@ 

+ + + + + +G + + + +n1 + + + + + +n5 + + + + +n1&%2345;&%2345;n5 + + + + + +n6 + + + + +n1&%2345;&%2345;n6 + + + + + +n7 + + + + +n1&%2345;&%2345;n7 + + + + + +n8 + + + + +n1&%2345;&%2345;n8 + + + + +n2 + + + + +n2&%2345;&%2345;n5 + + + + +n2&%2345;&%2345;n6 + + + + +n2&%2345;&%2345;n8 + + + + + +n9 + + + + +n2&%2345;&%2345;n9 + + + + + +n10 + + + + +n2&%2345;&%2345;n10 + + + + +n3 + + + + +n3&%2345;&%2345;n5 + + + + +n3&%2345;&%2345;n6 + + + + +n3&%2345;&%2345;n8 + + + + +n3&%2345;&%2345;n10 + + + + + +n11 + + + + +n3&%2345;&%2345;n11 + + + + +n4 + + + + +n4&%2345;&%2345;n7 + + + + +n4&%2345;&%2345;n9 + + + + +n4&%2345;&%2345;n11 + + + + +'/>
isotropic_nonhydrostatic_buoyancy = apex(oapply(nonhydrostatic_composition, [
   Open(momentum,          [:V, :v, :b, :StressDivergence]),
   Open(isotropic_tracer,  [:continuity_V, :v, :c, :turbulence_StressDivergence, :turbulence_nu]),
   Open(isotropic_tracer,  [:continuity_V, :v, :c, :turbulence_StressDivergence, :turbulence_nu]),
-  Open(equation_of_state, [:b, :T, :S])]));
-DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:80, TVar:3, Op1:20, Op2:35, Σ:6, Summand:14, Type:0, Operator:0, Name:0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vartypename
1DualForm1v
2DualForm1V
3Form0momentum_f
4DualForm1momentum_uˢ
5DualForm1momentum_∂tuˢ
6DualForm0momentum_p
7DualForm0b
8DualForm1momentum_ĝ
9DualForm1momentum_Fᵥ
10DualForm1SD
11infermomentum_v̇
12infermomentum_•2
13infermomentum_•3
14infermomentum_•4
15infermomentum_•5
16infermomentum_•6
17infermomentum_•7
18infermomentum_•8
19Literal0.5
20infermomentum_•9
21infermomentum_•10
22infermomentum_sum_1
23infermomentum_•11
24infermomentum_•12
25infermomentum_•13
26infermomentum_•14
27infermomentum_•15
28infermomentum_•16
29infermomentum_sum_2
30infermomentum_•17
31infermomentum_•18
32infermomentum_•19
33infermomentum_•20
34infermomentum_•21
35infermomentum_sum_3
36DualForm0T
37DualForm0temperature_FD
38Constanttemperature_turbulence_κ
39Constantnu
40infertemperature_turbulence_•1
41infertemperature_turbulence_•2
42DualForm0temperature_continuity_C
43DualForm0temperature_continuity_F
44infertemperature_continuity_ċ
45infertemperature_continuity_•2
46infertemperature_continuity_•3
47infertemperature_continuity_•4
48infertemperature_continuity_•5
49Literal-1
50infertemperature_continuity_•6
51infertemperature_continuity_•7
52infertemperature_continuity_•8
53infertemperature_continuity_•9
54infertemperature_continuity_•10
55infertemperature_continuity_•11
56DualForm0S
57DualForm0salinity_FD
58Constantsalinity_turbulence_κ
59infersalinity_turbulence_•1
60infersalinity_turbulence_•2
61DualForm0salinity_continuity_C
62DualForm0salinity_continuity_F
63infersalinity_continuity_ċ
64infersalinity_continuity_•2
65infersalinity_continuity_•3
66infersalinity_continuity_•4
67infersalinity_continuity_•5
68Literal-1
69infersalinity_continuity_•6
70infersalinity_continuity_•7
71infersalinity_continuity_•8
72infersalinity_continuity_•9
73infersalinity_continuity_•10
74infersalinity_continuity_•11
75Constanteos_g
76Constanteos_α
77Constanteos_β
78infereos_•1
79infereos_•2
80infereos_•3
- - - - - - - - - - - - - - - - - - - - - -
TVarincl
111
244
363
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op1srctgtop1
1111∂ₜ
21716-
32120d
42423d
5226d
6128d
7432[:d, :⋆]
8633d
9141Δᵈ₁
103640Δᵈ₀
113644∂ₜ
123651d
133653d
144255d
15160Δᵈ₁
165659Δᵈ₀
175663∂ₜ
185670d
195672d
206174d
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Op2proj1proj2resop2
11117ℒ₁
21121ι₁₁
3192018*
41224ι₁₁
5222315-
612625ι₁₂
722827ι₁₂
833231-
931130∧ᵖᵈ₀₁
10293014-
11143313-
127834∧ᵈᵈ₀₁
13351012-
14394110*
15384037*
1615150ι₁₁
17495048*
1825352ι₁₁
19485247-
2015554ι₁₁
21475446-
22463745-
23396010*
24585957*
2517069ι₁₁
26686967*
2727271ι₁₁
28677166-
2917473ι₁₁
30667365-
31655764-
32763680*
33775678*
34807879-
3575797*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Σsum
122
229
335
411
544
663
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Summandsummandsummation
1161
2181
3152
4252
5272
6133
7343
8124
954
1094
11455
12435
13646
14626
-
-

Meshes and Initial Conditions

Let's execute these dynamics on the torus explicitly, instead of using a square with periodic boundary conditions.

# This is a torus with resolution of its dual mesh similar to that
+  Open(equation_of_state, [:b, :T, :S])]));
+to_graphviz(isotropic_nonhydrostatic_buoyancy)
Example block output

Meshes and Initial Conditions

Let's execute these dynamics on the torus explicitly, instead of using a square with periodic boundary conditions.

# This is a torus with resolution of its dual mesh similar to that
 # used by Oceananigans (explicitly represented as a torus, not as a
 # square with periodic boundary conditions!)
 download("https://cise.ufl.edu/~luke.morris/torus.obj", "torus.obj")
@@ -2451,4 +612,4 @@ 

"speed.gif"

Vorticity

Speed

+save_speed(false)
"speed.gif"

Vorticity

Speed

diff --git a/dev/overview/index.html b/dev/overview/index.html index e4fee15d..e136fe78 100644 --- a/dev/overview/index.html +++ b/dev/overview/index.html @@ -791,4 +791,4 @@ # Animation 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"

```

+end
"diff_adv.gif"

```

diff --git a/dev/poiseuille/index.html b/dev/poiseuille/index.html index a075b658..4614a848 100644 --- a/dev/poiseuille/index.html +++ b/dev/poiseuille/index.html @@ -322,4 +322,4 @@ ComponentVector{Float64}(q = [3.229362018659291e13, -4.4685304924377445e13, 1.0014092632828035e13, 2.2834088276778594e12, 9.403502228976639e10, 1.4825473826853013e8, 373.8634401164856, 6.748007171994456, 6.6042640031841255, 6.922786733820605, 6.6829572669112, 6.945403323059754, 6.711137618204396, 6.656096082264105, 6.479123002819498, 6.147464654651846, 6.039361831653792, 5.6521478183729394, 6.154271690686454], ρ = [5.0, 4.830200898521725e28, -1.8534412189088976e28, -3.5561970013599265e27, -1.4087121553692516e26, -2.2174718843308503e23, -5.4945482749183546e17, -3.492932710488087e6, 147.24610690703432, 56.36699131399747, 74.77129872544127, 19.02091967888934, 7.773620537454371, 24.816711099028957, -7.713715579416296, 9.350784798692635, -41.205540068909954, -6.373736972480156, -107.83102822687177, 5.0]) 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.

+ 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.

diff --git a/dev/search_index.js b/dev/search_index.js index e3ef6191..5e273b92 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"bc_debug/#Simulation-Setup","page":"Misc Features","title":"Simulation Setup","text":"","category":"section"},{"location":"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 BC (Bounday Condition) component. Decapodes.jl interprets any Hom which begins with a ∂ as a boundary condition. These boundary conditions recieve special treatment at the scheduling step. 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_debug/","page":"Misc Features","title":"Misc Features","text":"using Catlab\nusing Catlab.Graphics\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\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_debug/","page":"Misc Features","title":"Misc Features","text":"As before, we compose these physics components over our wiring diagram.","category":"page"},{"location":"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\nto_graphviz(compose_diff_adv, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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_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_debug/","page":"Misc Features","title":"Misc Features","text":"In the visualization below, wee 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_debug/","page":"Misc Features","title":"Misc Features","text":"to_graphviz(DiffusionAdvection)","category":"page"},{"location":"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_debug/","page":"Misc Features","title":"Misc Features","text":"Rectangle_30x10 is a default mesh that is downloaded via Artifacts.jl when a user installs Decapodes. Via CombinatorialSpaces.jl, we can instantiate any .obj file of triangulated faces as a simplicial set.","category":"page"},{"location":"bc_debug/","page":"Misc Features","title":"Misc Features","text":"using CombinatorialSpaces, CombinatorialSpaces.DiscreteExteriorCalculus\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# 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_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. The modified initial condition is shown below:","category":"page"},{"location":"bc_debug/","page":"Misc Features","title":"Misc Features","text":"using LinearAlgebra\nusing ComponentArrays\nusing MLStyle\nusing CombinatorialSpaces.DiscreteExteriorCalculus: ∧\ninclude(\"../../examples/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,y) -> begin\n ∧(Tuple{0,1}, sd, x,y)\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\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_debug/","page":"Misc Features","title":"Misc Features","text":"And the simulation result is then computed and visualized below.","category":"page"},{"location":"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_debug/","page":"Misc Features","title":"Misc Features","text":"(Image: )","category":"page"},{"location":"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/","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/","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/","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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\n\n# External Dependencies\nusing ComponentArrays\nusing MLStyle\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing SparseArrays\nusing Statistics\nusing CairoMakie\nusing BenchmarkTools\nusing GeometryBasics: Point2, Point3\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64}; # hide","category":"page"},{"location":"cism/#Specifying-and-Composing-Physics","page":"CISM v2.1","title":"Specifying and Composing Physics","text":"","category":"section"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Equation 2\")","category":"page"},{"location":"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/","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/","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/","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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Glen's Law\")","category":"page"},{"location":"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/","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/","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/","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\nto_graphviz(ice_dynamics_composition_diagram, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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/","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/#Providing-a-Semantics","page":"CISM v2.1","title":"Providing a Semantics","text":"","category":"section"},{"location":"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/","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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"s′ = triangulated_grid(60_000,100_000,2_000,2_000,Point3D)\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\nx̄ = mean(p -> p[1], point(s))\nȳ = mean(p -> p[2], point(s))\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\nwf = wireframe!(ax, s)\nsave(\"ice_mesh.png\", fig)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Wireframe of the Domain\")","category":"page"},{"location":"cism/#Defining-input-data","page":"CISM v2.1","title":"Defining input data","text":"","category":"section"},{"location":"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/","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(s))\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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Initial Conditions\")","category":"page"},{"location":"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/","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/","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 -> begin\n norm.(x)\n end\n :^ => (x,y) -> begin\n x .^ y\n end\n _ => default_dec_matrix_generate(sd, my_symbol, hodge)\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"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/","page":"CISM v2.1","title":"CISM v2.1","text":"#######################\n# Generate simulation #\n#######################\n\nsim = eval(gensim(ice_dynamics2D))\nfₘ = sim(s, generate)","category":"page"},{"location":"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/","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/","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())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"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/","page":"CISM v2.1","title":"CISM v2.1","text":"# Time the simulation\n\nb = @benchmarkable solve(prob, Tsit5())\nc = run(b)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Save the solution information to a file.\n@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Small Ice Approximation Quote\")","category":"page"},{"location":"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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numerical Solution\")","category":"page"},{"location":"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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Analytic Solution)","category":"page"},{"location":"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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numeric Solution - Analytic Solution\")","category":"page"},{"location":"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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"We compute root meand square error (RMSE) as well, both over the entire domain, and excluding where the ice distribution is 0 in the analytic solution. (Considering the entire domain decreases the RMSE, while not telling you much about the dynamics in the area of interest.) Note that the official CISM benchmark reports 6.43 and 9.06 RMSE for their two solver implementations.","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMSE 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/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMSE 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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Ice Dynamics)","category":"page"},{"location":"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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: CISM Results)","category":"page"},{"location":"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 during a hackathon.","category":"page"},{"location":"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/","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":"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.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𝐮::Form1\n \n(P, 𝑝ᵈ)::Form0\n \n(negone, half, μ)::Constant\n \n∂ₜ(𝐮) == 𝐮̇\n \n𝑝ᵈ == P + half * i(𝐮, 𝐮)\n \n𝐮̇ == μ * (∘(d, ⋆, d, ⋆))(𝐮) + negone * (⋆₁⁻¹)(𝐮 ∧₁₀ₚᵈ (⋆)(d(𝐮))) + 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":"Schoedinger\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{X}\n \n(U2V, One)::Form0{X}\n \n(U̇, V̇)::Form0{X}\n \nα::Constant{X}\n \nF::Parameter{X}\n \nU2V == (U .* U) .* V\n \nU̇ == ((1 + U2V) - 4.4U) + α * Δ(U) + F\n \nV̇ == (3.4U - U2V) + α * Δ(U)\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":"nhs/#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/","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/","page":"NHS","title":"NHS","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};","category":"page"},{"location":"nhs/#Specify-our-models.","page":"NHS","title":"Specify our models.","text":"","category":"section"},{"location":"nhs/","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/","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","category":"page"},{"location":"nhs/","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: ∇⋅τ = nuΔ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/","page":"NHS","title":"NHS","text":"This is Equation 2: \"The tracer conservation equation\".","category":"page"},{"location":"nhs/","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","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Linear equation of state\" of seawater buoyancy.","category":"page"},{"location":"nhs/","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","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Constant isotropic diffusivity\".","category":"page"},{"location":"nhs/","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","category":"page"},{"location":"nhs/#Compatibility-Guarantees-via-Operadic-Composition","page":"NHS","title":"Compatibility Guarantees via Operadic Composition","text":"","category":"section"},{"location":"nhs/","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 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/","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/","page":"NHS","title":"NHS","text":"For example:","category":"page"},{"location":"nhs/","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/","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/","page":"NHS","title":"NHS","text":"Here, we specify the equations that any tracer obeys:","category":"page"},{"location":"nhs/","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","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"Let's \"lock in\" isotropic diffusivity by doing an intermediate oapply.","category":"page"},{"location":"nhs/","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])]))","category":"page"},{"location":"nhs/","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/","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\n\nisotropic_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])]));","category":"page"},{"location":"nhs/#Meshes-and-Initial-Conditions","page":"NHS","title":"Meshes and Initial Conditions","text":"","category":"section"},{"location":"nhs/","page":"NHS","title":"NHS","text":"Let's execute these dynamics on the torus explicitly, instead of using a square with periodic boundary conditions.","category":"page"},{"location":"nhs/","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!)\ndownload(\"https://cise.ufl.edu/~luke.morris/torus.obj\", \"torus.obj\")\ns = EmbeddedDeltaSet2D(\"torus.obj\")\nsd = EmbeddedDeltaDualComplex2D{Bool,Float64,Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n _ => default_dec_matrix_generate(sd, my_symbol, hodge)\n end\n return (args...) -> op(args...)\nend\n\nopen(\"nhs.jl\", \"w\") do f\n write(f, string(gensim(expand_operators(isotropic_nonhydrostatic_buoyancy))))\nend\nsim = include(\"nhs.jl\")\nfₘ = sim(sd, generate)\n\nS = map(sd[sd[:tri_center], :dual_point]) do (_,_,_)\n 0.0\nend\nT = map(sd[sd[:tri_center], :dual_point]) do (_,_,_)\n 0.0\nend\np = map(sd[sd[:tri_center], :dual_point]) do (_,_,_)\n 0.0\nend\nf = zeros(nv(sd))\nFₛ = zeros(ntriangles(sd))\nFₜ = zeros(ntriangles(sd))\nCₛ = zeros(ntriangles(sd))\nCₜ = zeros(ntriangles(sd))\nV = zeros(ne(sd))\nv = rand(ne(sd)) * 1e-8\nĝ = ♭(sd, DualVectorField(fill(Point3D(0,0,0), ntriangles(sd)))).data\nFᵥ = zeros(ne(sd))\nqₛ = zeros(ne(sd))\nqₜ = zeros(ne(sd))\nuˢ = zeros(ne(sd))\n∂tuˢ = zeros(ne(sd))\n\nu₀ = ComponentArray(\n v = v,\n V = V,\n momentum_f = f,\n momentum_uˢ = uˢ,\n momentum_∂tuˢ = ∂tuˢ,\n momentum_p = p,\n momentum_ĝ = ĝ,\n momentum_Fᵥ = Fᵥ,\n T = T,\n temperature_continuity_C = Cₜ,\n temperature_continuity_F = Fₜ,\n S = S,\n salinity_continuity_C = Cₛ,\n salinity_continuity_F = Fₛ)\n\ngᶜ = 9.81\nα = 2e-3\nβ = 5e-4\nconstants_and_parameters = (\n temperature_turbulence_κ = 0.0,\n salinity_turbulence_κ = 0.0,\n nu = 1e-5,\n eos_g = gᶜ,\n eos_α = α,\n eos_β = β);","category":"page"},{"location":"nhs/#Execute-the-Simulation","page":"NHS","title":"Execute the Simulation","text":"","category":"section"},{"location":"nhs/","page":"NHS","title":"NHS","text":"We specified our physics, our mesh, and our initial conditions. We have everything we need to execute the simulation.","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"tₑ = 50\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(), force_dtmin=true, dtmax=0.2, progress=true,progress_steps=1)\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"nhs/#Results","page":"NHS","title":"Results","text":"","category":"section"},{"location":"nhs/","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/","page":"NHS","title":"NHS","text":"ihs0 = dec_inv_hodge_star(Val{0}, sd, GeometricHodge())\ndd1 = dec_dual_derivative(1, sd)\n♯_m = ♯_mat(sd, LLSDDSharp())\nusing LinearAlgebra: norm\nfunction vorticity(α)\n ihs0*dd1*α\nend\nfunction speed(α)\n norm.(♯_m * α)\nend\n\nfunction save_vorticity(is_2d=false)\n frames = 200\n time = Observable(0.0)\n fig = Figure(title = @lift(\"Vorticity at $($time)\"))\n ax = is_2d ?\n CairoMakie.Axis(fig[1,1]) :\n LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.mesh!(ax, s,\n color=@lift(vorticity(soln($time).v)),\n colorrange=extrema(vorticity(soln(tₑ).v)).*.9,\n colormap=:jet)\n\n Colorbar(fig[1,2], msh)\n record(fig, \"vorticity.gif\", range(0.0, tₑ; length=frames); framerate = 20) do t\n time[] = t\n end\nend\nsave_vorticity(false)\n\nfunction save_speed(is_2d=false) frames = 200\n time = Observable(0.0)\n fig = Figure(title = @lift(\"Speed at $($time)\"))\n ax = is_2d ?\n CairoMakie.Axis(fig[1,1]) :\n LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.scatter!(ax, sd[sd[:tri_center], :dual_point],\n color=@lift(speed(soln($time).v)),\n colorrange=extrema(speed(soln(tₑ).v)).*.9,\n colormap=:jet,\n markersize=5)\n\n Colorbar(fig[1,2], msh)\n record(fig, \"speed.gif\", range(0.0, tₑ; length=frames); framerate = 20) do t\n time[] = t\n end\nend\nsave_speed(false)","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"(Image: Vorticity)","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"(Image: Speed)","category":"page"},{"location":"api/#Library-Reference","page":"Library Reference","title":"Library Reference","text":"","category":"section"},{"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.CartesianPoint","page":"Library Reference","title":"Decapodes.CartesianPoint","text":"CartesianPoint{T}(p)\n\na point in cartesian coordinates, intended as a wrapper around Point3 from GeometryBasics.\n\n\n\n\n\n","category":"type"},{"location":"api/#Decapodes.SpherePoint","page":"Library Reference","title":"Decapodes.SpherePoint","text":"SpherePoint{T}(p)\n\na point in spherical coordinates, intended as a wrapper around Point3 from GeometryBasics.\n\n\n\n\n\n","category":"type"},{"location":"api/#Decapodes.TangentBasis-Tuple{Any}","page":"Library Reference","title":"Decapodes.TangentBasis","text":"tb(w)\n\nTake a linear combinations of the tangent vectors at the base point. Use this to get a vector tangent to the sphere in the coordinate system of the base point. If the base point is in spherical coordinates, this is the identity, if the base point is in cartesian coordinates, it returns the tangent vector in cartesian coordinates.\n\n\n\n\n\n","category":"method"},{"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":"equations/#Simple-Equations","page":"Equations","title":"Simple Equations","text":"","category":"section"},{"location":"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/","page":"Equations","title":"Equations","text":"using Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing CombinatorialSpaces.ExteriorCalculus\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes","category":"page"},{"location":"equations/","page":"Equations","title":"Equations","text":"The harmonic oscillator can be written in Decapodes in at least three different ways.","category":"page"},{"location":"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/","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/","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/","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/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"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/","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/","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/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"equations/","page":"Equations","title":"Equations","text":"You can also represent negation as a multiplication by a literal -1.","category":"page"},{"location":"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/","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/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"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/","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/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"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":"ice_dynamics/#Halfar's-model-of-glacial-flow","page":"Glacial Flow","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# External Dependencies\nusing MLStyle\nusing ComponentArrays\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing SparseArrays\nusing Statistics\nusing CairoMakie\nusing GeometryBasics: Point2, Point3\nPoint2D = Point2{Float64};\nPoint3D = Point3{Float64};","category":"page"},{"location":"ice_dynamics/#Defining-the-models","page":"Glacial Flow","title":"Defining the models","text":"","category":"section"},{"location":"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/","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/","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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"In the exterior calculus, we could write the above equations like so:","category":"page"},{"location":"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/","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/","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/","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/","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/#Composing-models","page":"Glacial Flow","title":"Composing models","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\nto_graphviz(ice_dynamics_composition_diagram, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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/","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/#Provide-a-semantics","page":"Glacial Flow","title":"Provide a semantics","text":"","category":"section"},{"location":"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/","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/#Define-a-mesh","page":"Glacial Flow","title":"Define a mesh","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"# This is a 1D mesh, consisting of edges and vertices.\ns′ = EmbeddedDeltaSet1D{Bool, Point2D}()\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′)\ns = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s′)\nsubdivide_duals!(s, Circumcenter())","category":"page"},{"location":"ice_dynamics/#Define-input-data","page":"Glacial Flow","title":"Define input data","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We need initial conditions to use for our simulation.","category":"page"},{"location":"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/","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/","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/#Define-functions","page":"Glacial Flow","title":"Define functions","text":"","category":"section"},{"location":"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/","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 -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :show => x -> begin\n @show x\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/#Generate-the-simulation","page":"Glacial Flow","title":"Generate the simulation","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Now, we have everything we need to generate our simulation:","category":"page"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics1D, dimension=1))\nfₘ = sim(s, generate)","category":"page"},{"location":"ice_dynamics/#Pre-compile-and-run","page":"Glacial Flow","title":"Pre-compile and run","text":"","category":"section"},{"location":"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/","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ₑ = 8e3\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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics1D.jld2\" soln","category":"page"},{"location":"ice_dynamics/#Visualize","page":"Glacial Flow","title":"Visualize","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's examine the final conditions:","category":"page"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's create a GIF to examine an animation of these dynamics:","category":"page"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics1D)","category":"page"},{"location":"ice_dynamics/#2D-Re-interpretation","page":"Glacial Flow","title":"2D Re-interpretation","text":"","category":"section"},{"location":"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/","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/","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/#Store-as-JSON","page":"Glacial Flow","title":"Store as JSON","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"write_json_acset(ice_dynamics2D, \"ice_dynamics2D.json\")","category":"page"},{"location":"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/#Define-our-mesh","page":"Glacial Flow","title":"Define our mesh","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"s′ = triangulated_grid(10_000,10_000,800,800,Point3D)\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s′)\nfig","category":"page"},{"location":"ice_dynamics/#Define-our-input-data","page":"Glacial Flow","title":"Define our input data","text":"","category":"section"},{"location":"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/","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/#Define-our-functions","page":"Glacial Flow","title":"Define our functions","text":"","category":"section"},{"location":"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 ♯(sd, EForm(x))\n end\n :mag => x -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :show => x -> begin\n @show x\n @show length(x)\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/#Generate-simulation","page":"Glacial Flow","title":"Generate simulation","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(s, generate)","category":"page"},{"location":"ice_dynamics/#Pre-compile-and-run-2","page":"Glacial Flow","title":"Pre-compile and run","text":"","category":"section"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"ice_dynamics/#Visualize-2","page":"Glacial Flow","title":"Visualize","text":"","category":"section"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2D)","category":"page"},{"location":"ice_dynamics/#2-Manifold-in-3D","page":"Glacial Flow","title":"2-Manifold in 3D","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"s′ = loadmesh(Icosphere(3, 10_000))\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\nwireframe(s)\n","category":"page"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(s, generate)","category":"page"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# For brevity's sake, we'll skip the pre-compilation cell.\n\ntₑ = 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/","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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2DSphere)","category":"page"},{"location":"klausmeier/#Klausmeier","page":"Klausmeier","title":"Klausmeier","text":"","category":"section"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"
\n \"Somaliland\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/#Background","page":"Klausmeier","title":"Background","text":"","category":"section"},{"location":"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/","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/","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/","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/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier GIF)","category":"page"},{"location":"klausmeier/#using-Decapodes","page":"Klausmeier","title":"using Decapodes","text":"","category":"section"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Load Dependencies\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\nusing Catlab\nusing CombinatorialSpaces\n\nusing Distributions\nusing CairoMakie\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing ComponentArrays\nusing OrdinaryDiffEq\nusing GeometryBasics: Point2\nPoint2D = Point2{Float64}","category":"page"},{"location":"klausmeier/#Model-Representation","page":"Klausmeier","title":"Model Representation","text":"","category":"section"},{"location":"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/","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/","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 + ν * ℒ(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 # hide","category":"page"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"# Specify Composition\ncompose_klausmeier = @relation () begin\n phyto(N, W)\n hydro(N, W)\nend\n\nto_graphviz(compose_klausmeier, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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/","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/","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/","page":"Klausmeier","title":"Klausmeier","text":"sim = eval(gensim(Klausmeier, dimension=1))","category":"page"},{"location":"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. We can also cache these ","category":"page"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"function generate(sd, my_symbol; hodge=DiagonalHodge())\n lap_mat = hodge_star(1,sd) * d(0,sd) * inv_hodge_star(0,sd) * dual_derivative(0,sd)\n dd_mat = dual_derivative(0,sd)\n ih_mat = inv_hodge_star(0,sd)\n h_mat = hodge_star(1,sd)\n ih_dd_mat = ih_mat * dd_mat\n op = @match my_symbol begin\n :Δ => x -> begin\n lap_mat * x\n end\n :ℒ => (x,y) -> begin\n h_mat * ∧(0,1,sd, ih_dd_mat * y, x)\n end\n :^ => (x,y) -> begin\n x .^ y\n end\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"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/","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/","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(7, 500)\n\nscatter(sd[:point])","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's pass our mesh and methods of generating operators to our simulation code.","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Instantiate Simulation\nfₘ = sim(sd, generate, DiagonalHodge())","category":"page"},{"location":"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/","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/","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/","page":"Klausmeier","title":"Klausmeier","text":"Let's execute our simulation.","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Run Simulation\ntₑ = 600.0\nprob = ODEProblem(fₘ, u₀, (0.0, tₑ), cs_ps)\nsol = solve(prob, Tsit5())\nsol.retcode","category":"page"},{"location":"klausmeier/#Animation","page":"Klausmeier","title":"Animation","text":"","category":"section"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"n = sol(0).N\nnₑ = sol(tₑ).N\nw = sol(0).W\nwₑ = sol(tₑ).W # hide","category":"page"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier)","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We can observe a few interesting phenomena that we wanted to capture:","category":"page"},{"location":"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/#Conclusion","page":"Klausmeier","title":"Conclusion","text":"","category":"section"},{"location":"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/#References","page":"Klausmeier","title":"References","text":"","category":"section"},{"location":"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/","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/","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/","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":"grigoriev/#Halfar's-model-of-glacial-flow","page":"Grigoriev Ice Cap","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"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/","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/","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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# External Dependencies\nusing FileIO \nusing Interpolations\nusing MLStyle\nusing ComponentArrays\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing SparseArrays\nusing CairoMakie\nusing GeometryBasics: Point2\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64}; # hide","category":"page"},{"location":"grigoriev/#Loading-a-Scientific-Dataset","page":"Grigoriev Ice Cap","title":"Loading a Scientific Dataset","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The ice thickness data is stored in a TIF. We have downloaded it locally, and load it using basic FileIO.","category":"page"},{"location":"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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"This data may appear to be a simple binary mask, but that is only because values with no ice are set to -Inf. We will account for this we interpolate our data.","category":"page"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"We use the Interpolations.jl library to interpolate this dataset:","category":"page"},{"location":"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# 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/","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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's generate a triangulated grid located at the appropriate coordinates:","category":"page"},{"location":"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# Generate the mesh with appropriate dimensions and resolution:\ns′ = triangulated_grid(\n MAX_X-MIN_X, MAX_Y-MIN_Y,\n RES_X, RES_Y, Point3D)\n# Shift it into place:\ns′[:point] = map(x -> x + Point3D(MIN_X, MIN_Y, 0), s′[:point])\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s)\ndisplay(fig)","category":"page"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The coordinates of a vertex are stored in s[:point]. Let's use our interpolator to assign ice thickness values to each vertex in the mesh:","category":"page"},{"location":"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(s))\n\nh₀ = map(s[: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,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"grigoriev/#Defining-and-Composing-Models","page":"Grigoriev Ice Cap","title":"Defining and Composing Models","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"For exposition on this Halfar Decapode, see our Glacial Flow docs page. You can skip ahead to the next section.","category":"page"},{"location":"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) * avg₀₁(mag(♯(d(h)))^(n-1)) * avg₀₁(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/#Define-our-functions","page":"Grigoriev Ice Cap","title":"Define our functions","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"include(\"sharp_op.jl\")\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n ♯_m = ♯_mat(sd)\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n op = @match my_symbol begin\n :♯ => x -> begin\n ♯(sd, EForm(x))\n end\n :mag => x -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :abs => x -> abs.(x)\n :show => x -> begin\n println(x)\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"grigoriev/#Generate-simulation","page":"Grigoriev Ice Cap","title":"Generate simulation","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"sim = eval(gensim(ice_dynamics, dimension=2))\nfₘ = sim(s, generate)","category":"page"},{"location":"grigoriev/#Run","page":"Grigoriev Ice Cap","title":"Run","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"tₑ = 1e1\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@save \"grigoriev.jld2\" soln","category":"page"},{"location":"grigoriev/#Results-and-Discussion","page":"Grigoriev Ice Cap","title":"Results and Discussion","text":"","category":"section"},{"location":"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/","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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_ICs) (Image: Grigoriev_FCs) (Image: Grigoriev_Dynamics)","category":"page"},{"location":"budyko_sellers_halfar/#Budko-Sellers-Halfar","page":"Budyko-Sellers-Halfar","title":"Budko-Sellers-Halfar","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# External Dependencies\nusing MLStyle\nusing ComponentArrays\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing CairoMakie\nusing GeometryBasics: Point2\nPoint2D = Point2{Float64};","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We defined the Budyko-Sellers and Halfar models in example scripts (soon to be turned into Docs pages) in the examples/climate folder of the main repository. We recall them here.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"include(\"../../examples/climate/budyko_sellers.jl\")","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"include(\"../../examples/climate/shallow_ice.jl\")","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers = apex(budyko_sellers_cospan)\nhalfar = apex(ice_dynamics_cospan)\ntrue # hide","category":"page"},{"location":"budyko_sellers_halfar/#Budyko-Sellers","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"to_graphviz(budyko_sellers)","category":"page"},{"location":"budyko_sellers_halfar/#Halfar","page":"Budyko-Sellers-Halfar","title":"Halfar","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"to_graphviz(halfar)","category":"page"},{"location":"budyko_sellers_halfar/#Warming","page":"Budyko-Sellers-Halfar","title":"Warming","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"This is a formula that computes A for use in the Halfar glacial dynamics, given T from the Budyko-Sellers model.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# Tₛ(ϕ,t) := Surface temperature\n# A(ϕ) := Longwave emissions at 0°C\nwarming = @decapode begin\n (Tₛ)::Form0\n (A)::Form1\n\n A == avg₀₁(5.8282*10^(-0.236 * Tₛ)*1.65e7)\n\nend\nto_graphviz(warming)","category":"page"},{"location":"budyko_sellers_halfar/#Composition","page":"Budyko-Sellers-Halfar","title":"Composition","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Observe that this composition technique is the same as that used in composing each of the Budyko-Sellers and Halfar models.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar_composition_diagram = @relation () begin\n budyko_sellers(Tₛ)\n\n warming(A, Tₛ)\n\n halfar(A)\nend\nto_graphviz(budyko_sellers_halfar_composition_diagram, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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":"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)\nto_graphviz(budyko_sellers_halfar)","category":"page"},{"location":"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.","category":"page"},{"location":"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":"budyko_sellers_halfar/#Defining-the-mesh","page":"Budyko-Sellers-Halfar","title":"Defining the mesh","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"s′ = EmbeddedDeltaSet1D{Bool, Point2D}()\n#add_vertices!(s′, 30, point=Point2D.(range(-π/2 + π/32, π/2 - π/32, length=30), 0))\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′)\ns = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s′)\nsubdivide_duals!(s, Circumcenter())","category":"page"},{"location":"budyko_sellers_halfar/#Define-input-data","page":"Budyko-Sellers-Halfar","title":"Define input data","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We need to supply initial conditions to our model. We create synthetic data here, although one may imagine that they could source this from their data repo of choice.","category":"page"},{"location":"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# 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":"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":"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_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":"budyko_sellers_halfar/#Symbols-to-functions","page":"Budyko-Sellers-Halfar","title":"Symbols to functions","text":"","category":"section"},{"location":"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 bewteen points, lines, and triangles on meshes known as simplicial sets. Thus, DEC operators are re-usable across any simplicial set.","category":"page"},{"location":"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 -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :show => x -> begin\n @show x\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"budyko_sellers_halfar/#Simulation-generation","page":"Budyko-Sellers-Halfar","title":"Simulation generation","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"sim = eval(gensim(budyko_sellers_halfar, dimension=1))\nfₘ = sim(s, generate)","category":"page"},{"location":"budyko_sellers_halfar/#Run-simulation","page":"Budyko-Sellers-Halfar","title":"Run simulation","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"tₑ = 1e6\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, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")\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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We can save the solution file to examine later.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"@save \"budyko_sellers_halfar.jld2\" soln","category":"page"},{"location":"budyko_sellers_halfar/#Visualize","page":"Budyko-Sellers-Halfar","title":"Visualize","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for temperature.","category":"page"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for ice height.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"lines(map(x -> x[1], point(s′)), soln(tₑ).halfar_h)","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Create animated GIFs of the temperature and ice height dynamics.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"begin\n# Initial frame\nframes = 100\nfig = Figure(; size = (800, 800))\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s′))))\nylims!(ax1, extrema(soln(tₑ).Tₛ))\nLabel(fig[1,1,Top()], \"Surface temperature, Tₛ, [C°]\")\nLabel(fig[2,1,Top()], \"Line plot of temperature from North to South pole, every $(tₑ/frames) time units\")\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(; size = (800, 800))\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s′))))\nylims!(ax1, extrema(soln(tₑ).halfar_h))\nLabel(fig[1,1,Top()], \"Ice height, h\")\nLabel(fig[2,1,Top()], \"Line plot of ice height from North to South pole, every $(tₑ/frames) time units\")\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_h)\nend\nend","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_Temperature)","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_IceHeight)","category":"page"},{"location":"overview/#Introduction-to-Decapodes","page":"Overview","title":"Introduction to Decapodes","text":"","category":"section"},{"location":"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. The Decapode 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/#Your-First-Decapode","page":"Overview","title":"Your First Decapode","text":"","category":"section"},{"location":"overview/","page":"Overview","title":"Overview","text":"We begin with the most basic Decapode, one which only includes a single variable. In the Decapode graphical paradigm, nodes represent variables and arrows represent operators which relate variables to each other. Since the Decapode 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 on some space, or in a discrete context, the values defined on points 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. Below, we provide a very simple Decapode with just a single variable C. and define a convenience function for visualization in later examples.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"using DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\nusing Catlab, Catlab.Graphics\n\nVariable = @decapode begin\n C::Form0\nend;\n\nto_graphviz(Variable)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"The resulting diagram contains a single node, showing the single variable in this system. We can then add a second variable:","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"TwoVariables = @decapode begin\n C::Form0\n dC::Form1\nend;\n\nto_graphviz(TwoVariables)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"And then can add some relationship between them. In this case, we make an equation which states that dC is the discrete derivative of C:","category":"page"},{"location":"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/","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 discrete derivative.","category":"page"},{"location":"overview/#A-Little-More-Complicated","page":"Overview","title":"A Little More Complicated","text":"","category":"section"},{"location":"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/","page":"Overview","title":"Overview","text":"Diffusion = @decapode begin\n (C, Ċ)::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\n # Diffusion equation\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\nend;\n\nto_graphviz(Diffusion)","category":"page"},{"location":"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/#Bring-in-the-Dynamics","page":"Overview","title":"Bring in the Dynamics","text":"","category":"section"},{"location":"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/","page":"Overview","title":"Overview","text":"We begin this process by importing a mesh. The mesh has been pre-generated within CombinatorialSpaces, and is generated such that it has periodic boundary conditions. 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/","page":"Overview","title":"Overview","text":"See CombinatorialSpaces.jl for mesh construction and importing utilities.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"using Catlab.CategoricalAlgebra\nusing CombinatorialSpaces, CombinatorialSpaces.DiscreteExteriorCalculus\nusing CairoMakie\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/","page":"Overview","title":"Overview","text":"With the mesh uploaded, we also need to convert the Decapode into something which can be scheduled with explicit time stepping. In order to do this, we take every variable which is the time derivative of another variable and trace back the operations needed to compute this. This process essentially generates a computation graph in the form of a directed wiring diagram.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"Since our diagram is already defined, we just need to define a function which implements each of these symbolic operators and pass them to a scheduler for generating the function.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"Note that we chose to define k as a function that multiplies by a value k. We could have alternately chosen to represent k as a Constant that we multiply by in the Decapode itself.","category":"page"},{"location":"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 (args...) -> op(args...)\nend","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"Next, we generate the simulation function using gen_sim and set up our initial conditions for this problem.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"sim = eval(gensim(Diffusion))\nfₘ = sim(periodic_mesh, generate, DiagonalHodge())\n\nusing 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/","page":"Overview","title":"Overview","text":"Finally, we solve this PDE problem using the Tsit5() solver and generate an animation of the result!","category":"page"},{"location":"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());\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, \"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/","page":"Overview","title":"Overview","text":"(Image: )","category":"page"},{"location":"overview/#Merging-Multiple-Physics","page":"Overview","title":"Merging Multiple Physics","text":"","category":"section"},{"location":"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/","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/","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 ϕ == ∧₀₁(C,V)\nend\n\nSuperposition = @decapode begin\n (C, Ċ)::Form0\n (ϕ, ϕ₁, ϕ₂)::Form1\n\n ϕ == ϕ₁ + ϕ₂\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\nend\ntrue # hide","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"to_graphviz(Diffusion)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"to_graphviz(Advection)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"to_graphviz(Superposition)","category":"page"},{"location":"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/","page":"Overview","title":"Overview","text":"compose_diff_adv = @relation (C, V) begin\n diffusion(C, ϕ₁)\n advection(C, ϕ₂, V)\n superposition(ϕ₁, ϕ₂, ϕ, C)\nend\n\nto_graphviz(compose_diff_adv, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"After this, the physics can be composed as follows:","category":"page"},{"location":"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/","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/","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/","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/","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,y) -> begin\n ∧(Tuple{0,1}, sd, x,y)\n end\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/","page":"Overview","title":"Overview","text":"(Image: ) ```","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":"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. ∇⋅","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":"#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 simulatable 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":"#NOTE","page":"Decapodes.jl","title":"NOTE","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"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!","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 avaiable in the examples folder of the Decapodes.jl GitHub repo, and will be added to this documentation site soon.","category":"page"},{"location":"#NOTE-2","page":"Decapodes.jl","title":"NOTE","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"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!","category":"page"},{"location":"poiseuille/#Poissuille-Flow-for-Fluid-Mechanics","page":"Pipe Flow","title":"Poissuille Flow for Fluid Mechanics","text":"","category":"section"},{"location":"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 noslip boundary condition and the geometry of the pipe enter a 1D equation in the form of a resistance term.","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using Catlab\nusing CombinatorialSpaces\nusing CombinatorialSpaces.ExteriorCalculus\nusing CombinatorialSpaces.DiscreteExteriorCalculus: ∧\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# Julia community libraries\nusing CairoMakie\nusing GeometryBasics\nusing LinearAlgebra\nusing OrdinaryDiffEq","category":"page"},{"location":"poiseuille/#Creating-the-Poiseuille-Equations","page":"Pipe Flow","title":"Creating the Poiseuille Equations","text":"","category":"section"},{"location":"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/","page":"Pipe Flow","title":"Pipe Flow","text":"# μ̃ = negative viscosity per unit area\n# R = drag of pipe boundary\n\nPoise = @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/#Defining-the-Semantics","page":"Pipe Flow","title":"Defining the Semantics","text":"","category":"section"},{"location":"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. ","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using MLStyle\ninclude(\"../../examples/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 :∧₀₁ => (x,y) -> begin\n ∧(0,1, sd, x,y)\n end\n :∂ρ => ρ -> begin\n ρ[1] = 0\n ρ[end] = 0\n ρ\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"poiseuille/#A-Single-Pipe-Segment","page":"Pipe Flow","title":"A Single Pipe Segment","text":"","category":"section"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"We create a mesh with one pipe segment to see if we get the right answer. This simulation can be validated with the Poiseuille equation for a single pipe. First we create the mesh.","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Point3D = Point3{Float64}\ns = 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/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"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)\nsol = solve(prob, Tsit5())\nsol.u","category":"page"},{"location":"poiseuille/#A-Linear-Pipe-with-Multiple-Segments","page":"Pipe Flow","title":"A Linear Pipe with Multiple Segments","text":"","category":"section"},{"location":"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/","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)\ntrue # hide","category":"page"},{"location":"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/","page":"Pipe Flow","title":"Pipe Flow","text":"Note that we do not generate new simulation code for Poiseuille flow with gensim again. We provide our new mesh so that our discrete differential operators can be instantiated.","category":"page"},{"location":"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/#A-Distribution-Network","page":"Pipe Flow","title":"A Distribution Network","text":"","category":"section"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"To model a distribution network such as residential drinking water or natural gas, 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/","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)\ntrue # hide","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"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/#Multiphysics","page":"Pipe Flow","title":"Multiphysics","text":"","category":"section"},{"location":"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/","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/","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 [PBHF22]. 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/","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/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we can create the mesh and solve the equation.","category":"page"},{"location":"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/","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":"bc_debug/#Simulation-Setup","page":"Misc Features","title":"Simulation Setup","text":"","category":"section"},{"location":"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 BC (Bounday Condition) component. Decapodes.jl interprets any Hom which begins with a ∂ as a boundary condition. These boundary conditions recieve special treatment at the scheduling step. 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_debug/","page":"Misc Features","title":"Misc Features","text":"using Catlab\nusing Catlab.Graphics\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\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_debug/","page":"Misc Features","title":"Misc Features","text":"As before, we compose these physics components over our wiring diagram.","category":"page"},{"location":"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\nto_graphviz(compose_diff_adv, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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_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_debug/","page":"Misc Features","title":"Misc Features","text":"In the visualization below, wee 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_debug/","page":"Misc Features","title":"Misc Features","text":"to_graphviz(DiffusionAdvection)","category":"page"},{"location":"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_debug/","page":"Misc Features","title":"Misc Features","text":"Rectangle_30x10 is a default mesh that is downloaded via Artifacts.jl when a user installs Decapodes. Via CombinatorialSpaces.jl, we can instantiate any .obj file of triangulated faces as a simplicial set.","category":"page"},{"location":"bc_debug/","page":"Misc Features","title":"Misc Features","text":"using CombinatorialSpaces, CombinatorialSpaces.DiscreteExteriorCalculus\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# 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_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. The modified initial condition is shown below:","category":"page"},{"location":"bc_debug/","page":"Misc Features","title":"Misc Features","text":"using LinearAlgebra\nusing ComponentArrays\nusing MLStyle\nusing CombinatorialSpaces.DiscreteExteriorCalculus: ∧\ninclude(\"../../examples/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,y) -> begin\n ∧(Tuple{0,1}, sd, x,y)\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\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_debug/","page":"Misc Features","title":"Misc Features","text":"And the simulation result is then computed and visualized below.","category":"page"},{"location":"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_debug/","page":"Misc Features","title":"Misc Features","text":"(Image: )","category":"page"},{"location":"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/","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/","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/","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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing Decapodes\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\n\n# External Dependencies\nusing ComponentArrays\nusing MLStyle\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing SparseArrays\nusing Statistics\nusing CairoMakie\nusing BenchmarkTools\nusing GeometryBasics: Point2, Point3\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64}; # hide","category":"page"},{"location":"cism/#Specifying-and-Composing-Physics","page":"CISM v2.1","title":"Specifying and Composing Physics","text":"","category":"section"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Equation 2\")","category":"page"},{"location":"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/","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/","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/","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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Glen's Law\")","category":"page"},{"location":"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/","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/","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/","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\nto_graphviz(ice_dynamics_composition_diagram, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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/","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/#Providing-a-Semantics","page":"CISM v2.1","title":"Providing a Semantics","text":"","category":"section"},{"location":"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/","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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"s′ = triangulated_grid(60_000,100_000,2_000,2_000,Point3D)\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\nx̄ = mean(p -> p[1], point(s))\nȳ = mean(p -> p[2], point(s))\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1], aspect=0.6, xticks = [0, 3e4, 6e4])\nwf = wireframe!(ax, s)\nsave(\"ice_mesh.png\", fig)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Wireframe of the Domain\")","category":"page"},{"location":"cism/#Defining-input-data","page":"CISM v2.1","title":"Defining input data","text":"","category":"section"},{"location":"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/","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(s))\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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Initial Conditions\")","category":"page"},{"location":"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/","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/","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 -> begin\n norm.(x)\n end\n :^ => (x,y) -> begin\n x .^ y\n end\n _ => default_dec_matrix_generate(sd, my_symbol, hodge)\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"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/","page":"CISM v2.1","title":"CISM v2.1","text":"#######################\n# Generate simulation #\n#######################\n\nsim = eval(gensim(ice_dynamics2D))\nfₘ = sim(s, generate)","category":"page"},{"location":"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/","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/","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())\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"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/","page":"CISM v2.1","title":"CISM v2.1","text":"# Time the simulation\n\nb = @benchmarkable solve(prob, Tsit5())\nc = run(b)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Save the solution information to a file.\n@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Halfar Small Ice Approximation Quote\")","category":"page"},{"location":"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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numerical Solution\")","category":"page"},{"location":"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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Analytic Solution)","category":"page"},{"location":"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)","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Numeric Solution - Analytic Solution\")","category":"page"},{"location":"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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"We compute root meand square error (RMSE) as well, both over the entire domain, and excluding where the ice distribution is 0 in the analytic solution. (Considering the entire domain decreases the RMSE, while not telling you much about the dynamics in the area of interest.) Note that the official CISM benchmark reports 6.43 and 9.06 RMSE for their two solver implementations.","category":"page"},{"location":"cism/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMSE 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/","page":"CISM v2.1","title":"CISM v2.1","text":"# Compute RMSE 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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: \"Ice Dynamics)","category":"page"},{"location":"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/","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/","page":"CISM v2.1","title":"CISM v2.1","text":"(Image: CISM Results)","category":"page"},{"location":"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 during a hackathon.","category":"page"},{"location":"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/","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":"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.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𝐮::Form1\n \n(P, 𝑝ᵈ)::Form0\n \n(negone, half, μ)::Constant\n \n∂ₜ(𝐮) == 𝐮̇\n \n𝑝ᵈ == P + half * i(𝐮, 𝐮)\n \n𝐮̇ == μ * (∘(d, ⋆, d, ⋆))(𝐮) + negone * (⋆₁⁻¹)(𝐮 ∧₁₀ₚᵈ (⋆)(d(𝐮))) + 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":"Schoedinger\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{X}\n \n(U2V, One)::Form0{X}\n \n(U̇, V̇)::Form0{X}\n \nα::Constant{X}\n \nF::Parameter{X}\n \nU2V == (U .* U) .* V\n \nU̇ == ((1 + U2V) - 4.4U) + α * Δ(U) + F\n \nV̇ == (3.4U - U2V) + α * Δ(U)\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":"nhs/#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/","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/","page":"NHS","title":"NHS","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};","category":"page"},{"location":"nhs/#Specify-our-models.","page":"NHS","title":"Specify our models.","text":"","category":"section"},{"location":"nhs/","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/","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/","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: ∇⋅τ = nuΔ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/","page":"NHS","title":"NHS","text":"This is Equation 2: \"The tracer conservation equation\".","category":"page"},{"location":"nhs/","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/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Linear equation of state\" of seawater buoyancy.","category":"page"},{"location":"nhs/","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/","page":"NHS","title":"NHS","text":"This is Equation 2: \"Constant isotropic diffusivity\".","category":"page"},{"location":"nhs/","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/#Compatibility-Guarantees-via-Operadic-Composition","page":"NHS","title":"Compatibility Guarantees via Operadic Composition","text":"","category":"section"},{"location":"nhs/","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 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/","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/","page":"NHS","title":"NHS","text":"For example:","category":"page"},{"location":"nhs/","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/","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/","page":"NHS","title":"NHS","text":"Here, we specify the equations that any tracer obeys:","category":"page"},{"location":"nhs/","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\nto_graphviz(tracer_composition)","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"Let's \"lock in\" isotropic diffusivity by doing an intermediate oapply.","category":"page"},{"location":"nhs/","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/","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/","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\nto_graphviz(nonhydrostatic_composition)","category":"page"},{"location":"nhs/","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/#Meshes-and-Initial-Conditions","page":"NHS","title":"Meshes and Initial Conditions","text":"","category":"section"},{"location":"nhs/","page":"NHS","title":"NHS","text":"Let's execute these dynamics on the torus explicitly, instead of using a square with periodic boundary conditions.","category":"page"},{"location":"nhs/","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!)\ndownload(\"https://cise.ufl.edu/~luke.morris/torus.obj\", \"torus.obj\")\ns = EmbeddedDeltaSet2D(\"torus.obj\")\nsd = EmbeddedDeltaDualComplex2D{Bool,Float64,Point3D}(s)\nsubdivide_duals!(sd, Barycenter())\n\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n op = @match my_symbol begin\n _ => default_dec_matrix_generate(sd, my_symbol, hodge)\n end\n return (args...) -> op(args...)\nend\n\nopen(\"nhs.jl\", \"w\") do f\n write(f, string(gensim(expand_operators(isotropic_nonhydrostatic_buoyancy))))\nend\nsim = include(\"nhs.jl\")\nfₘ = sim(sd, generate)\n\nS = map(sd[sd[:tri_center], :dual_point]) do (_,_,_)\n 0.0\nend\nT = map(sd[sd[:tri_center], :dual_point]) do (_,_,_)\n 0.0\nend\np = map(sd[sd[:tri_center], :dual_point]) do (_,_,_)\n 0.0\nend\nf = zeros(nv(sd))\nFₛ = zeros(ntriangles(sd))\nFₜ = zeros(ntriangles(sd))\nCₛ = zeros(ntriangles(sd))\nCₜ = zeros(ntriangles(sd))\nV = zeros(ne(sd))\nv = rand(ne(sd)) * 1e-8\nĝ = ♭(sd, DualVectorField(fill(Point3D(0,0,0), ntriangles(sd)))).data\nFᵥ = zeros(ne(sd))\nqₛ = zeros(ne(sd))\nqₜ = zeros(ne(sd))\nuˢ = zeros(ne(sd))\n∂tuˢ = zeros(ne(sd))\n\nu₀ = ComponentArray(\n v = v,\n V = V,\n momentum_f = f,\n momentum_uˢ = uˢ,\n momentum_∂tuˢ = ∂tuˢ,\n momentum_p = p,\n momentum_ĝ = ĝ,\n momentum_Fᵥ = Fᵥ,\n T = T,\n temperature_continuity_C = Cₜ,\n temperature_continuity_F = Fₜ,\n S = S,\n salinity_continuity_C = Cₛ,\n salinity_continuity_F = Fₛ)\n\ngᶜ = 9.81\nα = 2e-3\nβ = 5e-4\nconstants_and_parameters = (\n temperature_turbulence_κ = 0.0,\n salinity_turbulence_κ = 0.0,\n nu = 1e-5,\n eos_g = gᶜ,\n eos_α = α,\n eos_β = β);","category":"page"},{"location":"nhs/#Execute-the-Simulation","page":"NHS","title":"Execute the Simulation","text":"","category":"section"},{"location":"nhs/","page":"NHS","title":"NHS","text":"We specified our physics, our mesh, and our initial conditions. We have everything we need to execute the simulation.","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"tₑ = 50\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(), force_dtmin=true, dtmax=0.2, progress=true,progress_steps=1)\n@show soln.retcode\n@info(\"Done\")","category":"page"},{"location":"nhs/#Results","page":"NHS","title":"Results","text":"","category":"section"},{"location":"nhs/","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/","page":"NHS","title":"NHS","text":"ihs0 = dec_inv_hodge_star(Val{0}, sd, GeometricHodge())\ndd1 = dec_dual_derivative(1, sd)\n♯_m = ♯_mat(sd, LLSDDSharp())\nusing LinearAlgebra: norm\nfunction vorticity(α)\n ihs0*dd1*α\nend\nfunction speed(α)\n norm.(♯_m * α)\nend\n\nfunction save_vorticity(is_2d=false)\n frames = 200\n time = Observable(0.0)\n fig = Figure(title = @lift(\"Vorticity at $($time)\"))\n ax = is_2d ?\n CairoMakie.Axis(fig[1,1]) :\n LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.mesh!(ax, s,\n color=@lift(vorticity(soln($time).v)),\n colorrange=extrema(vorticity(soln(tₑ).v)).*.9,\n colormap=:jet)\n\n Colorbar(fig[1,2], msh)\n record(fig, \"vorticity.gif\", range(0.0, tₑ; length=frames); framerate = 20) do t\n time[] = t\n end\nend\nsave_vorticity(false)\n\nfunction save_speed(is_2d=false) frames = 200\n time = Observable(0.0)\n fig = Figure(title = @lift(\"Speed at $($time)\"))\n ax = is_2d ?\n CairoMakie.Axis(fig[1,1]) :\n LScene(fig[1,1], scenekw=(lights=[],))\n msh = CairoMakie.scatter!(ax, sd[sd[:tri_center], :dual_point],\n color=@lift(speed(soln($time).v)),\n colorrange=extrema(speed(soln(tₑ).v)).*.9,\n colormap=:jet,\n markersize=5)\n\n Colorbar(fig[1,2], msh)\n record(fig, \"speed.gif\", range(0.0, tₑ; length=frames); framerate = 20) do t\n time[] = t\n end\nend\nsave_speed(false)","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"(Image: Vorticity)","category":"page"},{"location":"nhs/","page":"NHS","title":"NHS","text":"(Image: Speed)","category":"page"},{"location":"api/#Library-Reference","page":"Library Reference","title":"Library Reference","text":"","category":"section"},{"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.CartesianPoint","page":"Library Reference","title":"Decapodes.CartesianPoint","text":"CartesianPoint{T}(p)\n\na point in cartesian coordinates, intended as a wrapper around Point3 from GeometryBasics.\n\n\n\n\n\n","category":"type"},{"location":"api/#Decapodes.SpherePoint","page":"Library Reference","title":"Decapodes.SpherePoint","text":"SpherePoint{T}(p)\n\na point in spherical coordinates, intended as a wrapper around Point3 from GeometryBasics.\n\n\n\n\n\n","category":"type"},{"location":"api/#Decapodes.TangentBasis-Tuple{Any}","page":"Library Reference","title":"Decapodes.TangentBasis","text":"tb(w)\n\nTake a linear combinations of the tangent vectors at the base point. Use this to get a vector tangent to the sphere in the coordinate system of the base point. If the base point is in spherical coordinates, this is the identity, if the base point is in cartesian coordinates, it returns the tangent vector in cartesian coordinates.\n\n\n\n\n\n","category":"method"},{"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":"equations/#Simple-Equations","page":"Equations","title":"Simple Equations","text":"","category":"section"},{"location":"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/","page":"Equations","title":"Equations","text":"using Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing CombinatorialSpaces.ExteriorCalculus\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes","category":"page"},{"location":"equations/","page":"Equations","title":"Equations","text":"The harmonic oscillator can be written in Decapodes in at least three different ways.","category":"page"},{"location":"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/","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/","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/","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/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"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/","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/","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/","page":"Equations","title":"Equations","text":"to_graphviz(oscillator)","category":"page"},{"location":"equations/","page":"Equations","title":"Equations","text":"You can also represent negation as a multiplication by a literal -1.","category":"page"},{"location":"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/","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/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"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/","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/","page":"Equations","title":"Equations","text":"infer_types!(oscillator)\nto_graphviz(oscillator)","category":"page"},{"location":"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":"ice_dynamics/#Halfar's-model-of-glacial-flow","page":"Glacial Flow","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# External Dependencies\nusing MLStyle\nusing ComponentArrays\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing SparseArrays\nusing Statistics\nusing CairoMakie\nusing GeometryBasics: Point2, Point3\nPoint2D = Point2{Float64};\nPoint3D = Point3{Float64};","category":"page"},{"location":"ice_dynamics/#Defining-the-models","page":"Glacial Flow","title":"Defining the models","text":"","category":"section"},{"location":"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/","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/","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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"In the exterior calculus, we could write the above equations like so:","category":"page"},{"location":"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/","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/","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/","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/","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/#Composing-models","page":"Glacial Flow","title":"Composing models","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"ice_dynamics_composition_diagram = @relation () begin\n dynamics(Γ,n)\n stress(Γ,n)\nend\n\nto_graphviz(ice_dynamics_composition_diagram, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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/","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/#Provide-a-semantics","page":"Glacial Flow","title":"Provide a semantics","text":"","category":"section"},{"location":"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/","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/#Define-a-mesh","page":"Glacial Flow","title":"Define a mesh","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"# This is a 1D mesh, consisting of edges and vertices.\ns′ = EmbeddedDeltaSet1D{Bool, Point2D}()\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′)\ns = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s′)\nsubdivide_duals!(s, Circumcenter())","category":"page"},{"location":"ice_dynamics/#Define-input-data","page":"Glacial Flow","title":"Define input data","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"We need initial conditions to use for our simulation.","category":"page"},{"location":"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/","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/","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/#Define-functions","page":"Glacial Flow","title":"Define functions","text":"","category":"section"},{"location":"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/","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 -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :show => x -> begin\n @show x\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/#Generate-the-simulation","page":"Glacial Flow","title":"Generate the simulation","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Now, we have everything we need to generate our simulation:","category":"page"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics1D, dimension=1))\nfₘ = sim(s, generate)","category":"page"},{"location":"ice_dynamics/#Pre-compile-and-run","page":"Glacial Flow","title":"Pre-compile and run","text":"","category":"section"},{"location":"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/","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ₑ = 8e3\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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics1D.jld2\" soln","category":"page"},{"location":"ice_dynamics/#Visualize","page":"Glacial Flow","title":"Visualize","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's examine the final conditions:","category":"page"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"Let's create a GIF to examine an animation of these dynamics:","category":"page"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics1D)","category":"page"},{"location":"ice_dynamics/#2D-Re-interpretation","page":"Glacial Flow","title":"2D Re-interpretation","text":"","category":"section"},{"location":"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/","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/","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/#Store-as-JSON","page":"Glacial Flow","title":"Store as JSON","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"write_json_acset(ice_dynamics2D, \"ice_dynamics2D.json\")","category":"page"},{"location":"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/#Define-our-mesh","page":"Glacial Flow","title":"Define our mesh","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"s′ = triangulated_grid(10_000,10_000,800,800,Point3D)\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s′)\nfig","category":"page"},{"location":"ice_dynamics/#Define-our-input-data","page":"Glacial Flow","title":"Define our input data","text":"","category":"section"},{"location":"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/","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/#Define-our-functions","page":"Glacial Flow","title":"Define our functions","text":"","category":"section"},{"location":"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 ♯(sd, EForm(x))\n end\n :mag => x -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :show => x -> begin\n @show x\n @show length(x)\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"ice_dynamics/#Generate-simulation","page":"Glacial Flow","title":"Generate simulation","text":"","category":"section"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(s, generate)","category":"page"},{"location":"ice_dynamics/#Pre-compile-and-run-2","page":"Glacial Flow","title":"Pre-compile and run","text":"","category":"section"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"@save \"ice_dynamics2D.jld2\" soln","category":"page"},{"location":"ice_dynamics/#Visualize-2","page":"Glacial Flow","title":"Visualize","text":"","category":"section"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2D)","category":"page"},{"location":"ice_dynamics/#2-Manifold-in-3D","page":"Glacial Flow","title":"2-Manifold in 3D","text":"","category":"section"},{"location":"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/","page":"Glacial Flow","title":"Glacial Flow","text":"s′ = loadmesh(Icosphere(3, 10_000))\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\nwireframe(s)\n","category":"page"},{"location":"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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"sim = eval(gensim(ice_dynamics2D, dimension=2))\nfₘ = sim(s, generate)","category":"page"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"# For brevity's sake, we'll skip the pre-compilation cell.\n\ntₑ = 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/","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/","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/","page":"Glacial Flow","title":"Glacial Flow","text":"(Image: IceDynamics2DSphere)","category":"page"},{"location":"klausmeier/#Klausmeier","page":"Klausmeier","title":"Klausmeier","text":"","category":"section"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"
\n \"Somaliland\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/#Background","page":"Klausmeier","title":"Background","text":"","category":"section"},{"location":"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/","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/","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/","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/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier GIF)","category":"page"},{"location":"klausmeier/#using-Decapodes","page":"Klausmeier","title":"using Decapodes","text":"","category":"section"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Load Dependencies\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\nusing Catlab\nusing CombinatorialSpaces\n\nusing Distributions\nusing CairoMakie\nusing JLD2\nusing LinearAlgebra\nusing MLStyle\nusing ComponentArrays\nusing OrdinaryDiffEq\nusing GeometryBasics: Point2\nPoint2D = Point2{Float64}","category":"page"},{"location":"klausmeier/#Model-Representation","page":"Klausmeier","title":"Model Representation","text":"","category":"section"},{"location":"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/","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/","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 + ν * ℒ(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 # hide","category":"page"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"# Specify Composition\ncompose_klausmeier = @relation () begin\n phyto(N, W)\n hydro(N, W)\nend\n\nto_graphviz(compose_klausmeier, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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/","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/","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/","page":"Klausmeier","title":"Klausmeier","text":"sim = eval(gensim(Klausmeier, dimension=1))","category":"page"},{"location":"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. We can also cache these ","category":"page"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"function generate(sd, my_symbol; hodge=DiagonalHodge())\n lap_mat = hodge_star(1,sd) * d(0,sd) * inv_hodge_star(0,sd) * dual_derivative(0,sd)\n dd_mat = dual_derivative(0,sd)\n ih_mat = inv_hodge_star(0,sd)\n h_mat = hodge_star(1,sd)\n ih_dd_mat = ih_mat * dd_mat\n op = @match my_symbol begin\n :Δ => x -> begin\n lap_mat * x\n end\n :ℒ => (x,y) -> begin\n h_mat * ∧(0,1,sd, ih_dd_mat * y, x)\n end\n :^ => (x,y) -> begin\n x .^ y\n end\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"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/","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/","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(7, 500)\n\nscatter(sd[:point])","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"Let's pass our mesh and methods of generating operators to our simulation code.","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Instantiate Simulation\nfₘ = sim(sd, generate, DiagonalHodge())","category":"page"},{"location":"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/","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/","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/","page":"Klausmeier","title":"Klausmeier","text":"Let's execute our simulation.","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"# Run Simulation\ntₑ = 600.0\nprob = ODEProblem(fₘ, u₀, (0.0, tₑ), cs_ps)\nsol = solve(prob, Tsit5())\nsol.retcode","category":"page"},{"location":"klausmeier/#Animation","page":"Klausmeier","title":"Animation","text":"","category":"section"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"n = sol(0).N\nnₑ = sol(tₑ).N\nw = sol(0).W\nwₑ = sol(tₑ).W # hide","category":"page"},{"location":"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/","page":"Klausmeier","title":"Klausmeier","text":"(Image: Klausmeier)","category":"page"},{"location":"klausmeier/","page":"Klausmeier","title":"Klausmeier","text":"We can observe a few interesting phenomena that we wanted to capture:","category":"page"},{"location":"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/#Conclusion","page":"Klausmeier","title":"Conclusion","text":"","category":"section"},{"location":"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/#References","page":"Klausmeier","title":"References","text":"","category":"section"},{"location":"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/","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/","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/","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":"grigoriev/#Halfar's-model-of-glacial-flow","page":"Grigoriev Ice Cap","title":"Halfar's model of glacial flow","text":"","category":"section"},{"location":"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/","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/","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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# External Dependencies\nusing FileIO \nusing Interpolations\nusing MLStyle\nusing ComponentArrays\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing SparseArrays\nusing CairoMakie\nusing GeometryBasics: Point2\nPoint2D = Point2{Float64}\nPoint3D = Point3{Float64}; # hide","category":"page"},{"location":"grigoriev/#Loading-a-Scientific-Dataset","page":"Grigoriev Ice Cap","title":"Loading a Scientific Dataset","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The ice thickness data is stored in a TIF. We have downloaded it locally, and load it using basic FileIO.","category":"page"},{"location":"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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"This data may appear to be a simple binary mask, but that is only because values with no ice are set to -Inf. We will account for this we interpolate our data.","category":"page"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"We use the Interpolations.jl library to interpolate this dataset:","category":"page"},{"location":"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# 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/","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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"Let's generate a triangulated grid located at the appropriate coordinates:","category":"page"},{"location":"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# Generate the mesh with appropriate dimensions and resolution:\ns′ = triangulated_grid(\n MAX_X-MIN_X, MAX_Y-MIN_Y,\n RES_X, RES_Y, Point3D)\n# Shift it into place:\ns′[:point] = map(x -> x + Point3D(MIN_X, MIN_Y, 0), s′[:point])\ns = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)\nsubdivide_duals!(s, Barycenter())\n\nfig = Figure()\nax = CairoMakie.Axis(fig[1,1])\nwf = wireframe!(ax, s)\ndisplay(fig)","category":"page"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"The coordinates of a vertex are stored in s[:point]. Let's use our interpolator to assign ice thickness values to each vertex in the mesh:","category":"page"},{"location":"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(s))\n\nh₀ = map(s[: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,\n stress_ρ = ρ,\n stress_g = g,\n stress_A = A)","category":"page"},{"location":"grigoriev/#Defining-and-Composing-Models","page":"Grigoriev Ice Cap","title":"Defining and Composing Models","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"For exposition on this Halfar Decapode, see our Glacial Flow docs page. You can skip ahead to the next section.","category":"page"},{"location":"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) * avg₀₁(mag(♯(d(h)))^(n-1)) * avg₀₁(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/#Define-our-functions","page":"Grigoriev Ice Cap","title":"Define our functions","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"include(\"sharp_op.jl\")\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n ♯_m = ♯_mat(sd)\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n op = @match my_symbol begin\n :♯ => x -> begin\n ♯(sd, EForm(x))\n end\n :mag => x -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :abs => x -> abs.(x)\n :show => x -> begin\n println(x)\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"grigoriev/#Generate-simulation","page":"Grigoriev Ice Cap","title":"Generate simulation","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"sim = eval(gensim(ice_dynamics, dimension=2))\nfₘ = sim(s, generate)","category":"page"},{"location":"grigoriev/#Run","page":"Grigoriev Ice Cap","title":"Run","text":"","category":"section"},{"location":"grigoriev/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"tₑ = 1e1\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@save \"grigoriev.jld2\" soln","category":"page"},{"location":"grigoriev/#Results-and-Discussion","page":"Grigoriev Ice Cap","title":"Results and Discussion","text":"","category":"section"},{"location":"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/","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/","page":"Grigoriev Ice Cap","title":"Grigoriev Ice Cap","text":"(Image: Grigoriev_ICs) (Image: Grigoriev_FCs) (Image: Grigoriev_Dynamics)","category":"page"},{"location":"budyko_sellers_halfar/#Budko-Sellers-Halfar","page":"Budyko-Sellers-Halfar","title":"Budko-Sellers-Halfar","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# AlgebraicJulia Dependencies\nusing Catlab\nusing Catlab.Graphics\nusing CombinatorialSpaces\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# External Dependencies\nusing MLStyle\nusing ComponentArrays\nusing LinearAlgebra\nusing OrdinaryDiffEq\nusing JLD2\nusing CairoMakie\nusing GeometryBasics: Point2\nPoint2D = Point2{Float64};","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We defined the Budyko-Sellers and Halfar models in example scripts (soon to be turned into Docs pages) in the examples/climate folder of the main repository. We recall them here.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"include(\"../../examples/climate/budyko_sellers.jl\")","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"include(\"../../examples/climate/shallow_ice.jl\")","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers = apex(budyko_sellers_cospan)\nhalfar = apex(ice_dynamics_cospan)\ntrue # hide","category":"page"},{"location":"budyko_sellers_halfar/#Budyko-Sellers","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"to_graphviz(budyko_sellers)","category":"page"},{"location":"budyko_sellers_halfar/#Halfar","page":"Budyko-Sellers-Halfar","title":"Halfar","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"to_graphviz(halfar)","category":"page"},{"location":"budyko_sellers_halfar/#Warming","page":"Budyko-Sellers-Halfar","title":"Warming","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"This is a formula that computes A for use in the Halfar glacial dynamics, given T from the Budyko-Sellers model.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"# Tₛ(ϕ,t) := Surface temperature\n# A(ϕ) := Longwave emissions at 0°C\nwarming = @decapode begin\n (Tₛ)::Form0\n (A)::Form1\n\n A == avg₀₁(5.8282*10^(-0.236 * Tₛ)*1.65e7)\n\nend\nto_graphviz(warming)","category":"page"},{"location":"budyko_sellers_halfar/#Composition","page":"Budyko-Sellers-Halfar","title":"Composition","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Observe that this composition technique is the same as that used in composing each of the Budyko-Sellers and Halfar models.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"budyko_sellers_halfar_composition_diagram = @relation () begin\n budyko_sellers(Tₛ)\n\n warming(A, Tₛ)\n\n halfar(A)\nend\nto_graphviz(budyko_sellers_halfar_composition_diagram, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"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":"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)\nto_graphviz(budyko_sellers_halfar)","category":"page"},{"location":"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.","category":"page"},{"location":"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":"budyko_sellers_halfar/#Defining-the-mesh","page":"Budyko-Sellers-Halfar","title":"Defining the mesh","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"s′ = EmbeddedDeltaSet1D{Bool, Point2D}()\n#add_vertices!(s′, 30, point=Point2D.(range(-π/2 + π/32, π/2 - π/32, length=30), 0))\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′)\ns = EmbeddedDeltaDualComplex1D{Bool, Float64, Point2D}(s′)\nsubdivide_duals!(s, Circumcenter())","category":"page"},{"location":"budyko_sellers_halfar/#Define-input-data","page":"Budyko-Sellers-Halfar","title":"Define input data","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We need to supply initial conditions to our model. We create synthetic data here, although one may imagine that they could source this from their data repo of choice.","category":"page"},{"location":"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# 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":"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":"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_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":"budyko_sellers_halfar/#Symbols-to-functions","page":"Budyko-Sellers-Halfar","title":"Symbols to functions","text":"","category":"section"},{"location":"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 bewteen points, lines, and triangles on meshes known as simplicial sets. Thus, DEC operators are re-usable across any simplicial set.","category":"page"},{"location":"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 -> begin\n norm.(x)\n end\n :avg₀₁ => x -> begin\n I = Vector{Int64}()\n J = Vector{Int64}()\n V = Vector{Float64}()\n for e in 1:ne(s)\n append!(J, [s[e,:∂v0],s[e,:∂v1]])\n append!(I, [e,e])\n append!(V, [0.5, 0.5])\n end\n avg_mat = sparse(I,J,V)\n avg_mat * x\n end\n :^ => (x,y) -> x .^ y\n :* => (x,y) -> x .* y\n :show => x -> begin\n @show x\n x\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"budyko_sellers_halfar/#Simulation-generation","page":"Budyko-Sellers-Halfar","title":"Simulation generation","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"sim = eval(gensim(budyko_sellers_halfar, dimension=1))\nfₘ = sim(s, generate)","category":"page"},{"location":"budyko_sellers_halfar/#Run-simulation","page":"Budyko-Sellers-Halfar","title":"Run simulation","text":"","category":"section"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"tₑ = 1e6\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, Tsit5())\nsoln.retcode != :Unstable || error(\"Solver was not stable\")\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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"We can save the solution file to examine later.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"@save \"budyko_sellers_halfar.jld2\" soln","category":"page"},{"location":"budyko_sellers_halfar/#Visualize","page":"Budyko-Sellers-Halfar","title":"Visualize","text":"","category":"section"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for temperature.","category":"page"},{"location":"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":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Quickly examine the final conditions for ice height.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"lines(map(x -> x[1], point(s′)), soln(tₑ).halfar_h)","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"Create animated GIFs of the temperature and ice height dynamics.","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"begin\n# Initial frame\nframes = 100\nfig = Figure(; size = (800, 800))\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s′))))\nylims!(ax1, extrema(soln(tₑ).Tₛ))\nLabel(fig[1,1,Top()], \"Surface temperature, Tₛ, [C°]\")\nLabel(fig[2,1,Top()], \"Line plot of temperature from North to South pole, every $(tₑ/frames) time units\")\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(; size = (800, 800))\nax1 = CairoMakie.Axis(fig[1,1])\nxlims!(ax1, extrema(map(x -> x[1], point(s′))))\nylims!(ax1, extrema(soln(tₑ).halfar_h))\nLabel(fig[1,1,Top()], \"Ice height, h\")\nLabel(fig[2,1,Top()], \"Line plot of ice height from North to South pole, every $(tₑ/frames) time units\")\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_h)\nend\nend","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_Temperature)","category":"page"},{"location":"budyko_sellers_halfar/","page":"Budyko-Sellers-Halfar","title":"Budyko-Sellers-Halfar","text":"(Image: BSH_IceHeight)","category":"page"},{"location":"overview/#Introduction-to-Decapodes","page":"Overview","title":"Introduction to Decapodes","text":"","category":"section"},{"location":"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. The Decapode 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/#Your-First-Decapode","page":"Overview","title":"Your First Decapode","text":"","category":"section"},{"location":"overview/","page":"Overview","title":"Overview","text":"We begin with the most basic Decapode, one which only includes a single variable. In the Decapode graphical paradigm, nodes represent variables and arrows represent operators which relate variables to each other. Since the Decapode 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 on some space, or in a discrete context, the values defined on points 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. Below, we provide a very simple Decapode with just a single variable C. and define a convenience function for visualization in later examples.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"using DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\nusing Catlab, Catlab.Graphics\n\nVariable = @decapode begin\n C::Form0\nend;\n\nto_graphviz(Variable)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"The resulting diagram contains a single node, showing the single variable in this system. We can then add a second variable:","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"TwoVariables = @decapode begin\n C::Form0\n dC::Form1\nend;\n\nto_graphviz(TwoVariables)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"And then can add some relationship between them. In this case, we make an equation which states that dC is the discrete derivative of C:","category":"page"},{"location":"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/","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 discrete derivative.","category":"page"},{"location":"overview/#A-Little-More-Complicated","page":"Overview","title":"A Little More Complicated","text":"","category":"section"},{"location":"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/","page":"Overview","title":"Overview","text":"Diffusion = @decapode begin\n (C, Ċ)::Form0\n ϕ::Form1\n\n # Fick's first law\n ϕ == k(d₀(C))\n # Diffusion equation\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\nend;\n\nto_graphviz(Diffusion)","category":"page"},{"location":"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/#Bring-in-the-Dynamics","page":"Overview","title":"Bring in the Dynamics","text":"","category":"section"},{"location":"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/","page":"Overview","title":"Overview","text":"We begin this process by importing a mesh. The mesh has been pre-generated within CombinatorialSpaces, and is generated such that it has periodic boundary conditions. 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/","page":"Overview","title":"Overview","text":"See CombinatorialSpaces.jl for mesh construction and importing utilities.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"using Catlab.CategoricalAlgebra\nusing CombinatorialSpaces, CombinatorialSpaces.DiscreteExteriorCalculus\nusing CairoMakie\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/","page":"Overview","title":"Overview","text":"With the mesh uploaded, we also need to convert the Decapode into something which can be scheduled with explicit time stepping. In order to do this, we take every variable which is the time derivative of another variable and trace back the operations needed to compute this. This process essentially generates a computation graph in the form of a directed wiring diagram.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"Since our diagram is already defined, we just need to define a function which implements each of these symbolic operators and pass them to a scheduler for generating the function.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"Note that we chose to define k as a function that multiplies by a value k. We could have alternately chosen to represent k as a Constant that we multiply by in the Decapode itself.","category":"page"},{"location":"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 (args...) -> op(args...)\nend","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"Next, we generate the simulation function using gen_sim and set up our initial conditions for this problem.","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"sim = eval(gensim(Diffusion))\nfₘ = sim(periodic_mesh, generate, DiagonalHodge())\n\nusing 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/","page":"Overview","title":"Overview","text":"Finally, we solve this PDE problem using the Tsit5() solver and generate an animation of the result!","category":"page"},{"location":"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());\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, \"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/","page":"Overview","title":"Overview","text":"(Image: )","category":"page"},{"location":"overview/#Merging-Multiple-Physics","page":"Overview","title":"Merging Multiple Physics","text":"","category":"section"},{"location":"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/","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/","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 ϕ == ∧₀₁(C,V)\nend\n\nSuperposition = @decapode begin\n (C, Ċ)::Form0\n (ϕ, ϕ₁, ϕ₂)::Form1\n\n ϕ == ϕ₁ + ϕ₂\n Ċ == ⋆₀⁻¹(dual_d₁(⋆₁(ϕ)))\n ∂ₜ(C) == Ċ\nend\ntrue # hide","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"to_graphviz(Diffusion)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"to_graphviz(Advection)","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"to_graphviz(Superposition)","category":"page"},{"location":"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/","page":"Overview","title":"Overview","text":"compose_diff_adv = @relation (C, V) begin\n diffusion(C, ϕ₁)\n advection(C, ϕ₂, V)\n superposition(ϕ₁, ϕ₂, ϕ, C)\nend\n\nto_graphviz(compose_diff_adv, box_labels=:name, junction_labels=:variable, prog=\"circo\")","category":"page"},{"location":"overview/","page":"Overview","title":"Overview","text":"After this, the physics can be composed as follows:","category":"page"},{"location":"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/","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/","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/","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/","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,y) -> begin\n ∧(Tuple{0,1}, sd, x,y)\n end\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/","page":"Overview","title":"Overview","text":"(Image: ) ```","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":"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. ∇⋅","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":"#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 simulatable 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":"#NOTE","page":"Decapodes.jl","title":"NOTE","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"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!","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 avaiable in the examples folder of the Decapodes.jl GitHub repo, and will be added to this documentation site soon.","category":"page"},{"location":"#NOTE-2","page":"Decapodes.jl","title":"NOTE","text":"","category":"section"},{"location":"","page":"Decapodes.jl","title":"Decapodes.jl","text":"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!","category":"page"},{"location":"poiseuille/#Poissuille-Flow-for-Fluid-Mechanics","page":"Pipe Flow","title":"Poissuille Flow for Fluid Mechanics","text":"","category":"section"},{"location":"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 noslip boundary condition and the geometry of the pipe enter a 1D equation in the form of a resistance term.","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using Catlab\nusing CombinatorialSpaces\nusing CombinatorialSpaces.ExteriorCalculus\nusing CombinatorialSpaces.DiscreteExteriorCalculus: ∧\nusing DiagrammaticEquations\nusing DiagrammaticEquations.Deca\nusing Decapodes\n\n# Julia community libraries\nusing CairoMakie\nusing GeometryBasics\nusing LinearAlgebra\nusing OrdinaryDiffEq","category":"page"},{"location":"poiseuille/#Creating-the-Poiseuille-Equations","page":"Pipe Flow","title":"Creating the Poiseuille Equations","text":"","category":"section"},{"location":"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/","page":"Pipe Flow","title":"Pipe Flow","text":"# μ̃ = negative viscosity per unit area\n# R = drag of pipe boundary\n\nPoise = @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/#Defining-the-Semantics","page":"Pipe Flow","title":"Defining the Semantics","text":"","category":"section"},{"location":"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. ","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"using MLStyle\ninclude(\"../../examples/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 :∧₀₁ => (x,y) -> begin\n ∧(0,1, sd, x,y)\n end\n :∂ρ => ρ -> begin\n ρ[1] = 0\n ρ[end] = 0\n ρ\n end\n x => error(\"Unmatched operator $my_symbol\")\n end\n return (args...) -> op(args...)\nend","category":"page"},{"location":"poiseuille/#A-Single-Pipe-Segment","page":"Pipe Flow","title":"A Single Pipe Segment","text":"","category":"section"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"We create a mesh with one pipe segment to see if we get the right answer. This simulation can be validated with the Poiseuille equation for a single pipe. First we create the mesh.","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Point3D = Point3{Float64}\ns = 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/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"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)\nsol = solve(prob, Tsit5())\nsol.u","category":"page"},{"location":"poiseuille/#A-Linear-Pipe-with-Multiple-Segments","page":"Pipe Flow","title":"A Linear Pipe with Multiple Segments","text":"","category":"section"},{"location":"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/","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)\ntrue # hide","category":"page"},{"location":"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/","page":"Pipe Flow","title":"Pipe Flow","text":"Note that we do not generate new simulation code for Poiseuille flow with gensim again. We provide our new mesh so that our discrete differential operators can be instantiated.","category":"page"},{"location":"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/#A-Distribution-Network","page":"Pipe Flow","title":"A Distribution Network","text":"","category":"section"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"To model a distribution network such as residential drinking water or natural gas, 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/","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)\ntrue # hide","category":"page"},{"location":"poiseuille/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we solve the equations.","category":"page"},{"location":"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/#Multiphysics","page":"Pipe Flow","title":"Multiphysics","text":"","category":"section"},{"location":"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/","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/","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 [PBHF22]. 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/","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/","page":"Pipe Flow","title":"Pipe Flow","text":"Then we can create the mesh and solve the equation.","category":"page"},{"location":"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/","page":"Pipe Flow","title":"Pipe Flow","text":"Notice that the solution contains both a vector of flows and a vector of pressures.","category":"page"}] } diff --git a/dev/speed.gif b/dev/speed.gif index ce8ec8d2..fbb9cc4b 100644 Binary files a/dev/speed.gif and b/dev/speed.gif differ diff --git a/dev/vorticity.gif b/dev/vorticity.gif index 30f31bc3..93fde20e 100644 Binary files a/dev/vorticity.gif and b/dev/vorticity.gif differ