diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 67dfea81..3d6b7da6 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.0","generation_timestamp":"2024-02-06T20:59:00","documenter_version":"1.2.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.0","generation_timestamp":"2024-02-06T16:42:36","documenter_version":"1.2.1"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index 4cbe1f77..acb6154c 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 df60a451..72b5f514 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/6ad043c1.png b/dev/bc_debug/6ad043c1.png new file mode 100644 index 00000000..d447ae9b Binary files /dev/null and b/dev/bc_debug/6ad043c1.png differ diff --git a/dev/bc_debug/7eed7f44.png b/dev/bc_debug/7eed7f44.png deleted file mode 100644 index 249ff1e6..00000000 Binary files a/dev/bc_debug/7eed7f44.png and /dev/null differ diff --git a/dev/bc_debug/8c83529c-001.svg b/dev/bc_debug/8c83529c-001.svg deleted file mode 100644 index ffb240cf..00000000 --- a/dev/bc_debug/8c83529c-001.svg +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - -G - - - -n1 - -C:Ω₀ - - - -n3 - -diffusion_•1:Ω• - - - -n1->n3 - - -d₀ - - - -n7 - -bc_Ċ:Ω• - - - -n1->n7 - - -∂ₜ - - - -n13 - -Ω₀×ΩC - - - -n1->n13 - - -π₁ - - - -n2 - -ϕ₁:Ω₁ - - - -n14 - -Σ1 - - - -n2->n14 - - - - - -n3->n2 - - -k - - - -n4 - -ϕ₂:Ω₁ - - - -n4->n14 - - - - - -n5 - -V:ΩC - - - -n5->n13 - - -π₂ - - - -n6 - -C_up:Ω₀ - - - -n6->n7 - - -∂C - - - -n12 - - - -n7->n12 - - - - - -n8 - -ϕ:Ω₁ - - - -n10 - -superposition_•1:Ω• - - - -n8->n10 - - -⋆₁ - - - -n9 - -superposition_sum_1:Ω• - - - -n9->n6 - - -⋆₀⁻¹ - - - -n10->n9 - - -dual_d₁ - - - -n11 - - - -n11->n1 - - - - - -n11->n5 - - - - - -n13->n4 - - -∧₀₁ - - - -n14->n8 - - -+ - - - diff --git a/dev/bc_debug/8c83529c.svg b/dev/bc_debug/8c83529c.svg deleted file mode 100644 index ffb240cf..00000000 --- a/dev/bc_debug/8c83529c.svg +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - -G - - - -n1 - -C:Ω₀ - - - -n3 - -diffusion_•1:Ω• - - - -n1->n3 - - -d₀ - - - -n7 - -bc_Ċ:Ω• - - - -n1->n7 - - -∂ₜ - - - -n13 - -Ω₀×ΩC - - - -n1->n13 - - -π₁ - - - -n2 - -ϕ₁:Ω₁ - - - -n14 - -Σ1 - - - -n2->n14 - - - - - -n3->n2 - - -k - - - -n4 - -ϕ₂:Ω₁ - - - -n4->n14 - - - - - -n5 - -V:ΩC - - - -n5->n13 - - -π₂ - - - -n6 - -C_up:Ω₀ - - - -n6->n7 - - -∂C - - - -n12 - - - -n7->n12 - - - - - -n8 - -ϕ:Ω₁ - - - -n10 - -superposition_•1:Ω• - - - -n8->n10 - - -⋆₁ - - - -n9 - -superposition_sum_1:Ω• - - - -n9->n6 - - -⋆₀⁻¹ - - - -n10->n9 - - -dual_d₁ - - - -n11 - - - -n11->n1 - - - - - -n11->n5 - - - - - -n13->n4 - - -∧₀₁ - - - -n14->n8 - - -+ - - - diff --git a/dev/bc_debug/c49e3dcd.png b/dev/bc_debug/c49e3dcd.png new file mode 100644 index 00000000..6078a7d6 Binary files /dev/null and b/dev/bc_debug/c49e3dcd.png differ diff --git a/dev/bc_debug/f722a2b3-001.svg b/dev/bc_debug/f722a2b3-001.svg new file mode 100644 index 00000000..25c460d6 --- /dev/null +++ b/dev/bc_debug/f722a2b3-001.svg @@ -0,0 +1,200 @@ + + + + + + +G + + + +n1 + +C:Ω₀ + + + +n3 + +diffusion_•1:Ω• + + + +n1->n3 + + +d₀ + + + +n7 + +bc_Ċ:Ω• + + + +n1->n7 + + +∂ₜ + + + +n13 + +Ω₀×ΩC + + + +n1->n13 + + +π₁ + + + +n2 + +ϕ₁:Ω₁ + + + +n14 + +Σ1 + + + +n2->n14 + + + + + +n3->n2 + + +k + + + +n4 + +ϕ₂:Ω₁ + + + +n4->n14 + + + + + +n5 + +V:ΩC + + + +n5->n13 + + +π₂ + + + +n6 + +C_up:Ω₀ + + + +n6->n7 + + +∂C + + + +n12 + + + +n7->n12 + + + + + +n8 + +ϕ:Ω₁ + + + +n10 + +superposition_•1:Ω• + + + +n8->n10 + + +⋆₁ + + + +n9 + +superposition_sum_1:Ω• + + + +n9->n6 + + +⋆₀⁻¹ + + + +n10->n9 + + +dual_d₁ + + + +n11 + + + +n11->n1 + + + + + +n11->n5 + + + + + +n13->n4 + + +∧₀₁ + + + +n14->n8 + + ++ + + + diff --git a/dev/bc_debug/f722a2b3.svg b/dev/bc_debug/f722a2b3.svg new file mode 100644 index 00000000..25c460d6 --- /dev/null +++ b/dev/bc_debug/f722a2b3.svg @@ -0,0 +1,200 @@ + + + + + + +G + + + +n1 + +C:Ω₀ + + + +n3 + +diffusion_•1:Ω• + + + +n1->n3 + + +d₀ + + + +n7 + +bc_Ċ:Ω• + + + +n1->n7 + + +∂ₜ + + + +n13 + +Ω₀×ΩC + + + +n1->n13 + + +π₁ + + + +n2 + +ϕ₁:Ω₁ + + + +n14 + +Σ1 + + + +n2->n14 + + + + + +n3->n2 + + +k + + + +n4 + +ϕ₂:Ω₁ + + + +n4->n14 + + + + + +n5 + +V:ΩC + + + +n5->n13 + + +π₂ + + + +n6 + +C_up:Ω₀ + + + +n6->n7 + + +∂C + + + +n12 + + + +n7->n12 + + + + + +n8 + +ϕ:Ω₁ + + + +n10 + +superposition_•1:Ω• + + + +n8->n10 + + +⋆₁ + + + +n9 + +superposition_sum_1:Ω• + + + +n9->n6 + + +⋆₀⁻¹ + + + +n10->n9 + + +dual_d₁ + + + +n11 + + + +n11->n1 + + + + + +n11->n5 + + + + + +n13->n4 + + +∧₀₁ + + + +n14->n8 + + ++ + + + diff --git a/dev/bc_debug/fe567c96.png b/dev/bc_debug/fe567c96.png deleted file mode 100644 index c8a36768..00000000 Binary files a/dev/bc_debug/fe567c96.png and /dev/null differ diff --git a/dev/bc_debug/index.html b/dev/bc_debug/index.html index 70be9be6..6076dd57 100644 --- a/dev/bc_debug/index.html +++ b/dev/bc_debug/index.html @@ -42,45 +42,45 @@ to_graphviz(BoundaryConditions) - - + G - + n1 - -diffusion + +diffusion n7 - -C + +C n1&%2345;&%2345;n7 - + n9 - -ϕ₁ + +ϕ₁ n1&%2345;&%2345;n9 - + n2 - -advection + +advection n2&%2345;&%2345;n7 - + n8 - -V + +V n2&%2345;&%2345;n8 - + n10 - -ϕ₂ + +ϕ₂ n2&%2345;&%2345;n10 - + n3 - -bc + +bc n3&%2345;&%2345;n7 - + n11 - -C_up + +C_up n3&%2345;&%2345;n11 - + n4 - -superposition + +superposition n4&%2345;&%2345;n7 - + n4&%2345;&%2345;n9 - + n4&%2345;&%2345;n10 - + n4&%2345;&%2345;n11 - + n12 - -ϕ + +ϕ n4&%2345;&%2345;n12 - + n5&%2345;&%2345;n7 - + n6&%2345;&%2345;n8 - + @@ -275,7 +275,7 @@ Open(Superposition, [:ϕ₁, :ϕ₂, :ϕ, :C_up, :C])]) DiffusionAdvection = apex(DiffusionAdvection_cospan) -to_graphviz(DiffusionAdvection)Example block output

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.

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.

to_graphviz(DiffusionAdvection)
Example block output

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.

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.

using CombinatorialSpaces, CombinatorialSpaces.DiscreteExteriorCalculus
+to_graphviz(DiffusionAdvection)
Example block output

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.

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.

to_graphviz(DiffusionAdvection)
Example block output

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.

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.

using CombinatorialSpaces, CombinatorialSpaces.DiscreteExteriorCalculus
 using CairoMakie
 
 plot_mesh = loadmesh(Rectangle_30x10())
@@ -285,9 +285,10 @@
 # Calculate distances and subdivisions for the dual mesh
 subdivide_duals!(plot_mesh_dual, Circumcenter())
 
-fig, ax, ob = wireframe(plot_mesh)
-ax.aspect = AxisAspect(3.0)
-fig
Example block output

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:

using LinearAlgebra
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+wireframe!(ax, plot_mesh)
+fig
Example block output

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:

using LinearAlgebra
 using ComponentArrays
 using MLStyle
 using CombinatorialSpaces.DiscreteExteriorCalculus: ∧
@@ -313,9 +314,10 @@
 c_dist = MvNormal([1, 5], [1.5, 1.5])
 c = [pdf(c_dist, [p[1], p[2]]) for p in plot_mesh_dual[:point]]
 
-fig, ax, ob = mesh(plot_mesh; color=c[1:nv(plot_mesh)])
-ax.aspect = AxisAspect(3.0)
-fig
Example block output

And the simulation result is then computed and visualized below.

using OrdinaryDiffEq
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+mesh!(ax, plot_mesh; color=c[1:nv(plot_mesh)])
+fig
Example block output

And the simulation result is then computed and visualized below.

using OrdinaryDiffEq
 
 sim = eval(gensim(DiffusionAdvection))
 fₘ = sim(plot_mesh_dual, generate)
@@ -334,12 +336,13 @@
 colors = [sol(t).C for t in times]
 extrema
 # Initial frame
-fig, ax, ob = mesh(plot_mesh, color=colors[1], colorrange = extrema(vcat(colors...)))
-ax.aspect = AxisAspect(3.0)
-Colorbar(fig[1,2], ob)
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+pmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))
+Colorbar(fig[1,2], pmsh)
 framerate = 30
 
 # Animation
 record(fig, "diff_adv_right.gif", range(0.0, 100.0; length=150); framerate = 30) do t
-  ob.color = sol(t).C
-end
"diff_adv_right.gif"

+ pmsh.color = sol(t).C +end
"diff_adv_right.gif"

diff --git a/dev/budyko_sellers.gif b/dev/budyko_sellers.gif index f58271ca..a593f7fc 100644 Binary files a/dev/budyko_sellers.gif and b/dev/budyko_sellers.gif differ diff --git a/dev/budyko_sellers.jld2 b/dev/budyko_sellers.jld2 index ea5712f4..c0673551 100644 Binary files a/dev/budyko_sellers.jld2 and b/dev/budyko_sellers.jld2 differ diff --git a/dev/budyko_sellers_halfar.jld2 b/dev/budyko_sellers_halfar.jld2 index c354dcbf..2805ac98 100644 Binary files a/dev/budyko_sellers_halfar.jld2 and b/dev/budyko_sellers_halfar.jld2 differ diff --git a/dev/budyko_sellers_halfar/0f8a8b71.svg b/dev/budyko_sellers_halfar/0f8a8b71.svg new file mode 100644 index 00000000..0365073f --- /dev/null +++ b/dev/budyko_sellers_halfar/0f8a8b71.svg @@ -0,0 +1,1319 @@ + + + + + + +G + + + +n1 + +Tₛ:Ω₀ + + + +n6 + +budyko_sellers_energy_Tₛ̇:Ω₀ + + + +n1->n6 + + +∂ₜ + + + +n19 + +budyko_sellers_diffusion_•1:Ω₁ + + + +n1->n19 + + +d₀ + + + +n68 + +ΩC×Ω₀ + + + +n1->n68 + + +π₂ + + + +n74 + +ΩL×Ω₀ + + + +n1->n74 + + +π₂ + + + +n2 + +budyko_sellers_ASR:Ω₀ + + + +n64 + +Ω₀×Ω₀ + + + +n2->n64 + + +π₁ + + + +n3 + +budyko_sellers_OLR:Ω₀ + + + +n3->n64 + + +π₂ + + + +n4 + +budyko_sellers_HT:Ω₀ + + + +n89 + +Σ1 + + + +n4->n89 + + + + + +n5 + +budyko_sellers_energy_C:ΩC + + + +n65 + +Ω₀×ΩC + + + +n5->n65 + + +π₂ + + + +n63 + + + +n6->n63 + + + + + +n7 + +budyko_sellers_energy_•1:Ω₀ + + + +n7->n89 + + + + + +n8 + +budyko_sellers_energy_sum_1:Ω₀ + + + +n8->n65 + + +π₁ + + + +n9 + +budyko_sellers_Q:Ω₀ + + + +n67 + +Ω₀×Ω₀ + + + +n9->n67 + + +π₂ + + + +n10 + +budyko_sellers_absorbed_radiation_α:ΩC + + + +n66 + +ΩL×ΩC + + + +n10->n66 + + +π₂ + + + +n11 + +1:ΩL + + + +n11->n66 + + +π₁ + + + +n12 + +budyko_sellers_absorbed_radiation_•1:Ω₀ + + + +n12->n67 + + +π₁ + + + +n13 + +budyko_sellers_outgoing_radiation_A:ΩC + + + +n69 + +ΩC×Ω₀ + + + +n13->n69 + + +π₁ + + + +n14 + +budyko_sellers_outgoing_radiation_B:ΩC + + + +n14->n68 + + +π₁ + + + +n15 + +budyko_sellers_outgoing_radiation_•1:Ω₀ + + + +n15->n69 + + +π₂ + + + +n16 + +budyko_sellers_diffusion_D:ΩC + + + +n70 + +ΩC×ΩC + + + +n16->n70 + + +π₁ + + + +n17 + +budyko_sellers_cosϕᵖ:ΩC + + + +n17->n70 + + +π₂ + + + +n73 + +ΩL×ΩC + + + +n17->n73 + + +π₂ + + + +n18 + +budyko_sellers_diffusion_cosϕᵈ:ΩC + + + +n71 + +ΩC×Ω̃₀ + + + +n18->n71 + + +π₁ + + + +n24 + +budyko_sellers_diffusion_•6:Ω̃₀ + + + +n19->n24 + + +⋆₁ + + + +n20 + +budyko_sellers_diffusion_•2:Ω₀ + + + +n72 + +Ω₀×Ω₀ + + + +n20->n72 + + +π₁ + + + +n21 + +budyko_sellers_diffusion_•3:Ω₀ + + + +n21->n72 + + +π₂ + + + +n22 + +budyko_sellers_diffusion_•4:Ω̃₁ + + + +n22->n21 + + +⋆₀⁻¹ + + + +n23 + +budyko_sellers_diffusion_•5:Ω̃₀ + + + +n23->n22 + + +dual_d₀ + + + +n24->n71 + + +π₂ + + + +n25 + +450:ΩL + + + +n25->n73 + + +π₁ + + + +n26 + +A:Ω₁ + + + +n87 + +Ω•×Ω₁ + + + +n26->n87 + + +π₂ + + + +n27 + +warming_mult_1:Ω• + + + +n27->n26 + + +avg₀₁ + + + +n28 + +5.8282:ΩL + + + +n76 + +ΩL×Ω• + + + +n28->n76 + + +π₁ + + + +n29 + +warming_•1:Ω• + + + +n29->n76 + + +π₂ + + + +n30 + +10:ΩL + + + +n75 + +ΩL×Ω• + + + +n30->n75 + + +π₁ + + + +n31 + +warming_•2:Ω• + + + +n31->n75 + + +π₂ + + + +n32 + +-0.236:ΩL + + + +n32->n74 + + +π₁ + + + +n33 + +1.65e7:ΩL + + + +n77 + +Ω•×ΩL + + + +n33->n77 + + +π₂ + + + +n34 + +warming_mult_2:Ω• + + + +n34->n77 + + +π₁ + + + +n35 + +halfar_h:Ω₀ + + + +n38 + +halfar_dynamics_ḣ:Ω• + + + +n35->n38 + + +∂ₜ + + + +n41 + +halfar_dynamics_•1:Ω• + + + +n35->n41 + + +d + + + +n46 + +halfar_dynamics_•6:Ω• + + + +n35->n46 + + +d + + + +n80 + +Ω₀×Ω• + + + +n35->n80 + + +π₁ + + + +n36 + +halfar_Γ:Ω₁ + + + +n81 + +Ω₁×Ω• + + + +n36->n81 + + +π₁ + + + +n37 + +halfar_n:ΩC + + + +n78 + +ΩC×ΩL + + + +n37->n78 + + +π₁ + + + +n86 + +Ω•×ΩC + + + +n37->n86 + + +π₂ + + + +n90 + +Σ2 + + + +n37->n90 + + + + + +n91 + +Σ3 + + + +n37->n91 + + + + + +n38->n63 + + + + + +n39 + +halfar_dynamics_mult_1:Ω• + + + +n83 + +Ω•×Ω• + + + +n39->n83 + + +π₁ + + + +n40 + +halfar_dynamics_mult_2:Ω• + + + +n40->n38 + + +⋆⋅d⋅⋆ + + + +n41->n81 + + +π₂ + + + +n42 + +halfar_dynamics_•2:Ω• + + + +n82 + +Ω•×Ω• + + + +n42->n82 + + +π₂ + + + +n43 + +halfar_dynamics_•3:Ω• + + + +n43->n42 + + +avg₀₁ + + + +n44 + +halfar_dynamics_•4:Ω• + + + +n79 + +Ω•×Ω• + + + +n44->n79 + + +π₁ + + + +n45 + +halfar_dynamics_•5:Ω• + + + +n45->n44 + + +mag + + + +n46->n45 + + + + + + +n47 + +halfar_dynamics_•7:Ω• + + + +n47->n79 + + +π₂ + + + +n48 + +1:ΩL + + + +n48->n78 + + +π₂ + + + +n49 + +halfar_dynamics_•8:Ω• + + + +n49->n83 + + +π₂ + + + +n50 + +halfar_dynamics_•9:Ω• + + + +n50->n49 + + +avg₀₁ + + + +n51 + +2:ΩL + + + +n51->n90 + + + + + +n52 + +halfar_dynamics_sum_1:Ω• + + + +n52->n80 + + +π₂ + + + +n53 + +halfar_dynamics_mult_3:Ω• + + + +n53->n82 + + +π₁ + + + +n54 + +halfar_stress_ρ:ΩC + + + +n85 + +ΩC×ΩC + + + +n54->n85 + + +π₁ + + + +n55 + +halfar_stress_g:ΩC + + + +n55->n85 + + +π₂ + + + +n56 + +halfar_stress_•1:Ω• + + + +n56->n87 + + +π₁ + + + +n57 + +2:ΩL + + + +n84 + +ΩL×Ω• + + + +n57->n84 + + +π₁ + + + +n57->n91 + + + + + +n58 + +halfar_stress_sum_1:Ω• + + + +n58->n84 + + +π₂ + + + +n59 + +halfar_stress_•2:Ω• + + + +n88 + +Ω•×Ω• + + + +n59->n88 + + +π₂ + + + +n60 + +halfar_stress_•3:Ω• + + + +n60->n86 + + +π₁ + + + +n61 + +halfar_stress_mult_1:Ω• + + + +n61->n88 + + +π₁ + + + +n62 + + + +n62->n1 + + + + + +n62->n5 + + + + + +n62->n10 + + + + + +n62->n13 + + + + + +n62->n14 + + + + + +n62->n16 + + + + + +n62->n17 + + + + + +n62->n18 + + + + + +n62->n35 + + + + + +n62->n37 + + + + + +n62->n54 + + + + + +n62->n55 + + + + + +n64->n7 + + +- + + + +n65->n6 + + +./ + + + +n66->n12 + + +.- + + + +n67->n2 + + +.* + + + +n68->n15 + + +.* + + + +n69->n3 + + +.+ + + + +n70->n20 + + +./ + + + +n71->n23 + + +.* + + + +n72->n4 + + +.* + + + +n73->n9 + + +* + + + +n74->n31 + + +* + + + +n75->n29 + + +^ + + + +n76->n34 + + +* + + + +n77->n27 + + +* + + + +n78->n47 + + +- + + + +n79->n43 + + +^ + + + +n80->n50 + + +^ + + + +n81->n53 + + +* + + + +n82->n39 + + +* + + + +n83->n40 + + +* + + + +n84->n56 + + +/ + + + +n85->n60 + + +* + + + +n86->n59 + + +^ + + + +n87->n61 + + +* + + + +n88->n36 + + +* + + + +n89->n8 + + ++ + + + +n90->n52 + + ++ + + + +n91->n58 + + ++ + + + diff --git a/dev/budyko_sellers_halfar/208f1732.png b/dev/budyko_sellers_halfar/208f1732.png new file mode 100644 index 00000000..d38ddb2e Binary files /dev/null and b/dev/budyko_sellers_halfar/208f1732.png differ diff --git a/dev/budyko_sellers_halfar/3856ebfd.png b/dev/budyko_sellers_halfar/3856ebfd.png deleted file mode 100644 index dee30a02..00000000 Binary files a/dev/budyko_sellers_halfar/3856ebfd.png and /dev/null differ diff --git a/dev/budyko_sellers_halfar/410ba158.svg b/dev/budyko_sellers_halfar/410ba158.svg new file mode 100644 index 00000000..68c9ac17 --- /dev/null +++ b/dev/budyko_sellers_halfar/410ba158.svg @@ -0,0 +1,555 @@ + + + + + + +G + + + +n1 + +Tₛ:Ω₀ + + + +n6 + +energy_Tₛ̇:Ω₀ + + + +n1->n6 + + +∂ₜ + + + +n19 + +diffusion_•1:Ω₁ + + + +n1->n19 + + +d₀ + + + +n32 + +ΩC×Ω₀ + + + +n1->n32 + + +π₂ + + + +n2 + +ASR:Ω₀ + + + +n28 + +Ω₀×Ω₀ + + + +n2->n28 + + +π₁ + + + +n3 + +OLR:Ω₀ + + + +n3->n28 + + +π₂ + + + +n4 + +HT:Ω₀ + + + +n38 + +Σ1 + + + +n4->n38 + + + + + +n5 + +energy_C:ΩC + + + +n29 + +Ω₀×ΩC + + + +n5->n29 + + +π₂ + + + +n27 + + + +n6->n27 + + + + + +n7 + +energy_•1:Ω₀ + + + +n7->n38 + + + + + +n8 + +energy_sum_1:Ω₀ + + + +n8->n29 + + +π₁ + + + +n9 + +Q:Ω₀ + + + +n31 + +Ω₀×Ω₀ + + + +n9->n31 + + +π₂ + + + +n10 + +absorbed_radiation_α:ΩC + + + +n30 + +ΩL×ΩC + + + +n10->n30 + + +π₂ + + + +n11 + +1:ΩL + + + +n11->n30 + + +π₁ + + + +n12 + +absorbed_radiation_•1:Ω₀ + + + +n12->n31 + + +π₁ + + + +n13 + +outgoing_radiation_A:ΩC + + + +n33 + +ΩC×Ω₀ + + + +n13->n33 + + +π₁ + + + +n14 + +outgoing_radiation_B:ΩC + + + +n14->n32 + + +π₁ + + + +n15 + +outgoing_radiation_•1:Ω₀ + + + +n15->n33 + + +π₂ + + + +n16 + +diffusion_D:ΩC + + + +n34 + +ΩC×ΩC + + + +n16->n34 + + +π₁ + + + +n17 + +cosϕᵖ:ΩC + + + +n17->n34 + + +π₂ + + + +n37 + +ΩL×ΩC + + + +n17->n37 + + +π₂ + + + +n18 + +diffusion_cosϕᵈ:ΩC + + + +n35 + +ΩC×Ω̃₀ + + + +n18->n35 + + +π₁ + + + +n24 + +diffusion_•6:Ω̃₀ + + + +n19->n24 + + +⋆₁ + + + +n20 + +diffusion_•2:Ω₀ + + + +n36 + +Ω₀×Ω₀ + + + +n20->n36 + + +π₁ + + + +n21 + +diffusion_•3:Ω₀ + + + +n21->n36 + + +π₂ + + + +n22 + +diffusion_•4:Ω̃₁ + + + +n22->n21 + + +⋆₀⁻¹ + + + +n23 + +diffusion_•5:Ω̃₀ + + + +n23->n22 + + +dual_d₀ + + + +n24->n35 + + +π₂ + + + +n25 + +450:ΩL + + + +n25->n37 + + +π₁ + + + +n26 + + + +n26->n1 + + + + + +n26->n5 + + + + + +n26->n10 + + + + + +n26->n13 + + + + + +n26->n14 + + + + + +n26->n16 + + + + + +n26->n17 + + + + + +n26->n18 + + + + + +n28->n7 + + +- + + + +n29->n6 + + +./ + + + +n30->n12 + + +.- + + + +n31->n2 + + +.* + + + +n32->n15 + + +.* + + + +n33->n3 + + +.+ + + + +n34->n20 + + +./ + + + +n35->n23 + + +.* + + + +n36->n4 + + +.* + + + +n37->n9 + + +* + + + +n38->n8 + + ++ + + + diff --git a/dev/budyko_sellers_halfar/49441688.svg b/dev/budyko_sellers_halfar/49441688.svg deleted file mode 100644 index 60dad901..00000000 --- a/dev/budyko_sellers_halfar/49441688.svg +++ /dev/null @@ -1,555 +0,0 @@ - - - - - - -G - - - -n1 - -Tₛ:Ω₀ - - - -n6 - -energy_Tₛ̇:Ω₀ - - - -n1->n6 - - -∂ₜ - - - -n19 - -diffusion_•1:Ω₁ - - - -n1->n19 - - -d₀ - - - -n32 - -ΩC×Ω₀ - - - -n1->n32 - - -π₂ - - - -n2 - -ASR:Ω₀ - - - -n28 - -Ω₀×Ω₀ - - - -n2->n28 - - -π₁ - - - -n3 - -OLR:Ω₀ - - - -n3->n28 - - -π₂ - - - -n4 - -HT:Ω₀ - - - -n38 - -Σ1 - - - -n4->n38 - - - - - -n5 - -energy_C:ΩC - - - -n29 - -Ω₀×ΩC - - - -n5->n29 - - -π₂ - - - -n27 - - - -n6->n27 - - - - - -n7 - -energy_•1:Ω₀ - - - -n7->n38 - - - - - -n8 - -energy_sum_1:Ω₀ - - - -n8->n29 - - -π₁ - - - -n9 - -Q:Ω₀ - - - -n31 - -Ω₀×Ω₀ - - - -n9->n31 - - -π₂ - - - -n10 - -absorbed_radiation_α:ΩC - - - -n30 - -ΩL×ΩC - - - -n10->n30 - - -π₂ - - - -n11 - -1:ΩL - - - -n11->n30 - - -π₁ - - - -n12 - -absorbed_radiation_•1:Ω₀ - - - -n12->n31 - - -π₁ - - - -n13 - -outgoing_radiation_A:ΩC - - - -n33 - -ΩC×Ω₀ - - - -n13->n33 - - -π₁ - - - -n14 - -outgoing_radiation_B:ΩC - - - -n14->n32 - - -π₁ - - - -n15 - -outgoing_radiation_•1:Ω₀ - - - -n15->n33 - - -π₂ - - - -n16 - -diffusion_D:ΩC - - - -n34 - -ΩC×ΩC - - - -n16->n34 - - -π₁ - - - -n17 - -cosϕᵖ:ΩC - - - -n17->n34 - - -π₂ - - - -n37 - -ΩL×ΩC - - - -n17->n37 - - -π₂ - - - -n18 - -diffusion_cosϕᵈ:ΩC - - - -n35 - -ΩC×Ω̃₀ - - - -n18->n35 - - -π₁ - - - -n24 - -diffusion_•6:Ω̃₀ - - - -n19->n24 - - -⋆₁ - - - -n20 - -diffusion_•2:Ω₀ - - - -n36 - -Ω₀×Ω₀ - - - -n20->n36 - - -π₁ - - - -n21 - -diffusion_•3:Ω₀ - - - -n21->n36 - - -π₂ - - - -n22 - -diffusion_•4:Ω̃₁ - - - -n22->n21 - - -⋆₀⁻¹ - - - -n23 - -diffusion_•5:Ω̃₀ - - - -n23->n22 - - -dual_d₀ - - - -n24->n35 - - -π₂ - - - -n25 - -450:ΩL - - - -n25->n37 - - -π₁ - - - -n26 - - - -n26->n1 - - - - - -n26->n5 - - - - - -n26->n10 - - - - - -n26->n13 - - - - - -n26->n14 - - - - - -n26->n16 - - - - - -n26->n17 - - - - - -n26->n18 - - - - - -n28->n7 - - -- - - - -n29->n6 - - -./ - - - -n30->n12 - - -.- - - - -n31->n2 - - -.* - - - -n32->n15 - - -.* - - - -n33->n3 - - -.+ - - - -n34->n20 - - -./ - - - -n35->n23 - - -.* - - - -n36->n4 - - -.* - - - -n37->n9 - - -* - - - -n38->n8 - - -+ - - - diff --git a/dev/budyko_sellers_halfar/4f466a8c.png b/dev/budyko_sellers_halfar/4f466a8c.png new file mode 100644 index 00000000..99a11a37 Binary files /dev/null and b/dev/budyko_sellers_halfar/4f466a8c.png differ diff --git a/dev/budyko_sellers_halfar/520eada1.svg b/dev/budyko_sellers_halfar/520eada1.svg new file mode 100644 index 00000000..fd21a18a --- /dev/null +++ b/dev/budyko_sellers_halfar/520eada1.svg @@ -0,0 +1,628 @@ + + + + + + +G + + + +n1 + +h:Ω₀ + + + +n4 + +dynamics_ḣ:Ω• + + + +n1->n4 + + +∂ₜ + + + +n7 + +dynamics_•1:Ω• + + + +n1->n7 + + +d + + + +n12 + +dynamics_•6:Ω• + + + +n1->n12 + + +d + + + +n33 + +Ω₀×Ω• + + + +n1->n33 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n34 + +Ω₁×Ω• + + + +n2->n34 + + +π₁ + + + +n3 + +n:ΩC + + + +n31 + +ΩC×ΩL + + + +n3->n31 + + +π₁ + + + +n39 + +Ω•×ΩC + + + +n3->n39 + + +π₂ + + + +n42 + +Σ1 + + + +n3->n42 + + + + + +n43 + +Σ2 + + + +n3->n43 + + + + + +n30 + + + +n4->n30 + + + + + +n5 + +dynamics_mult_1:Ω• + + + +n36 + +Ω•×Ω• + + + +n5->n36 + + +π₁ + + + +n6 + +dynamics_mult_2:Ω• + + + +n6->n4 + + +⋆⋅d⋅⋆ + + + +n7->n34 + + +π₂ + + + +n8 + +dynamics_•2:Ω• + + + +n35 + +Ω•×Ω• + + + +n8->n35 + + +π₂ + + + +n9 + +dynamics_•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +dynamics_•4:Ω• + + + +n32 + +Ω•×Ω• + + + +n10->n32 + + +π₁ + + + +n11 + +dynamics_•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +dynamics_•7:Ω• + + + +n13->n32 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n31 + + +π₂ + + + +n15 + +dynamics_•8:Ω• + + + +n15->n36 + + +π₂ + + + +n16 + +dynamics_•9:Ω• + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n42 + + + + + +n18 + +dynamics_sum_1:Ω• + + + +n18->n33 + + +π₂ + + + +n19 + +dynamics_mult_3:Ω• + + + +n19->n35 + + +π₁ + + + +n20 + +stress_A:Ω₁ + + + +n40 + +Ω•×Ω₁ + + + +n20->n40 + + +π₂ + + + +n21 + +stress_ρ:ΩC + + + +n38 + +ΩC×ΩC + + + +n21->n38 + + +π₁ + + + +n22 + +stress_g:ΩC + + + +n22->n38 + + +π₂ + + + +n23 + +stress_•1:Ω• + + + +n23->n40 + + +π₁ + + + +n24 + +2:ΩL + + + +n37 + +ΩL×Ω• + + + +n24->n37 + + +π₁ + + + +n24->n43 + + + + + +n25 + +stress_sum_1:Ω• + + + +n25->n37 + + +π₂ + + + +n26 + +stress_•2:Ω• + + + +n41 + +Ω•×Ω• + + + +n26->n41 + + +π₂ + + + +n27 + +stress_•3:Ω• + + + +n27->n39 + + +π₁ + + + +n28 + +stress_mult_1:Ω• + + + +n28->n41 + + +π₁ + + + +n29 + + + +n29->n1 + + + + + +n29->n3 + + + + + +n29->n20 + + + + + +n29->n21 + + + + + +n29->n22 + + + + + +n31->n13 + + +- + + + +n32->n9 + + +^ + + + +n33->n16 + + +^ + + + +n34->n19 + + +* + + + +n35->n5 + + +* + + + +n36->n6 + + +* + + + +n37->n23 + + +/ + + + +n38->n27 + + +* + + + +n39->n26 + + +^ + + + +n40->n28 + + +* + + + +n41->n2 + + +* + + + +n42->n18 + + ++ + + + +n43->n25 + + ++ + + + diff --git a/dev/budyko_sellers_halfar/5232cbe3.png b/dev/budyko_sellers_halfar/5232cbe3.png new file mode 100644 index 00000000..049deed2 Binary files /dev/null and b/dev/budyko_sellers_halfar/5232cbe3.png differ diff --git a/dev/budyko_sellers_halfar/58832439.svg b/dev/budyko_sellers_halfar/58832439.svg new file mode 100644 index 00000000..73a62487 --- /dev/null +++ b/dev/budyko_sellers_halfar/58832439.svg @@ -0,0 +1,202 @@ + + + + + + +G + + + +n1 + +Tₛ:Ω₀ + + + +n13 + +ΩL×Ω₀ + + + +n1->n13 + + +π₂ + + + +n2 + +A:Ω₁ + + + +n3 + +mult_1:Ω• + + + +n3->n2 + + +avg₀₁ + + + +n4 + +5.8282:ΩL + + + +n15 + +ΩL×Ω• + + + +n4->n15 + + +π₁ + + + +n5 + +•1:Ω• + + + +n5->n15 + + +π₂ + + + +n6 + +10:ΩL + + + +n14 + +ΩL×Ω• + + + +n6->n14 + + +π₁ + + + +n7 + +•2:Ω• + + + +n7->n14 + + +π₂ + + + +n8 + +-0.236:ΩL + + + +n8->n13 + + +π₁ + + + +n9 + +1.65e7:ΩL + + + +n16 + +Ω•×ΩL + + + +n9->n16 + + +π₂ + + + +n10 + +mult_2:Ω• + + + +n10->n16 + + +π₁ + + + +n11 + + + +n11->n1 + + + + + +n12 + + + +n13->n7 + + +* + + + +n14->n5 + + +^ + + + +n15->n10 + + +* + + + +n16->n3 + + +* + + + diff --git a/dev/budyko_sellers_halfar/67548d9c.png b/dev/budyko_sellers_halfar/67548d9c.png new file mode 100644 index 00000000..67b76256 Binary files /dev/null and b/dev/budyko_sellers_halfar/67548d9c.png differ diff --git a/dev/budyko_sellers_halfar/719ec93c.svg b/dev/budyko_sellers_halfar/719ec93c.svg new file mode 100644 index 00000000..723247fe --- /dev/null +++ b/dev/budyko_sellers_halfar/719ec93c.svg @@ -0,0 +1,1345 @@ + + + + + + +G + + + +n1 + +Tₛ:Ω₀ + + + +n6 + +budyko_sellers_energy_Tₛ̇:Ω₀ + + + +n1->n6 + + +∂ₜ + + + +n19 + +budyko_sellers_diffusion_•1:Ω₁ + + + +n1->n19 + + +d₀ + + + +n70 + +ΩC×Ω₀ + + + +n1->n70 + + +π₂ + + + +n76 + +ΩL×Ω₀ + + + +n1->n76 + + +π₂ + + + +n2 + +budyko_sellers_ASR:Ω₀ + + + +n66 + +Ω₀×Ω₀ + + + +n2->n66 + + +π₁ + + + +n3 + +budyko_sellers_OLR:Ω₀ + + + +n3->n66 + + +π₂ + + + +n4 + +budyko_sellers_HT:Ω₀ + + + +n91 + +Σ1 + + + +n4->n91 + + + + + +n5 + +budyko_sellers_energy_C:ΩC + + + +n67 + +Ω₀×ΩC + + + +n5->n67 + + +π₂ + + + +n65 + + + +n6->n65 + + + + + +n7 + +budyko_sellers_energy_•1:Ω₀ + + + +n7->n91 + + + + + +n8 + +budyko_sellers_energy_sum_1:Ω₀ + + + +n8->n67 + + +π₁ + + + +n9 + +budyko_sellers_Q:Ω₀ + + + +n69 + +Ω₀×Ω₀ + + + +n9->n69 + + +π₂ + + + +n10 + +budyko_sellers_absorbed_radiation_α:ΩC + + + +n68 + +ΩL×ΩC + + + +n10->n68 + + +π₂ + + + +n11 + +1:ΩL + + + +n11->n68 + + +π₁ + + + +n12 + +budyko_sellers_absorbed_radiation_•1:Ω₀ + + + +n12->n69 + + +π₁ + + + +n13 + +budyko_sellers_outgoing_radiation_A:ΩC + + + +n71 + +ΩC×Ω₀ + + + +n13->n71 + + +π₁ + + + +n14 + +budyko_sellers_outgoing_radiation_B:ΩC + + + +n14->n70 + + +π₁ + + + +n15 + +budyko_sellers_outgoing_radiation_•1:Ω₀ + + + +n15->n71 + + +π₂ + + + +n16 + +budyko_sellers_diffusion_D:ΩC + + + +n72 + +ΩC×ΩC + + + +n16->n72 + + +π₁ + + + +n17 + +budyko_sellers_cosϕᵖ:ΩC + + + +n17->n72 + + +π₂ + + + +n75 + +ΩL×ΩC + + + +n17->n75 + + +π₂ + + + +n18 + +budyko_sellers_diffusion_cosϕᵈ:ΩC + + + +n73 + +ΩC×Ω̃₀ + + + +n18->n73 + + +π₁ + + + +n24 + +budyko_sellers_diffusion_•6:Ω̃₀ + + + +n19->n24 + + +⋆₁ + + + +n20 + +budyko_sellers_diffusion_•2:Ω₀ + + + +n74 + +Ω₀×Ω₀ + + + +n20->n74 + + +π₁ + + + +n21 + +budyko_sellers_diffusion_•3:Ω₀ + + + +n21->n74 + + +π₂ + + + +n22 + +budyko_sellers_diffusion_•4:Ω̃₁ + + + +n22->n21 + + +⋆₀⁻¹ + + + +n23 + +budyko_sellers_diffusion_•5:Ω̃₀ + + + +n23->n22 + + +dual_d₀ + + + +n24->n73 + + +π₂ + + + +n25 + +450:ΩL + + + +n25->n75 + + +π₁ + + + +n26 + +A:Ω₁ + + + +n89 + +Ω•×Ω₁ + + + +n26->n89 + + +π₂ + + + +n27 + +warming_mult_1:Ω₀ + + + +n27->n26 + + +avg₀₁ + + + +n28 + +5.8282:ΩL + + + +n78 + +ΩL×Ω₀ + + + +n28->n78 + + +π₁ + + + +n29 + +warming_•1:Ω₀ + + + +n29->n78 + + +π₂ + + + +n30 + +10:ΩL + + + +n77 + +ΩL×Ω₀ + + + +n30->n77 + + +π₁ + + + +n31 + +warming_•2:Ω₀ + + + +n31->n77 + + +π₂ + + + +n32 + +-0.236:ΩL + + + +n32->n76 + + +π₁ + + + +n33 + +1.65e7:ΩL + + + +n79 + +Ω₀×ΩL + + + +n33->n79 + + +π₂ + + + +n34 + +warming_mult_2:Ω₀ + + + +n34->n79 + + +π₁ + + + +n35 + +halfar_h:Ω₀ + + + +n38 + +halfar_dynamics_ḣ:Ω₀ + + + +n35->n38 + + +∂ₜ + + + +n41 + +halfar_dynamics_•1:Ω₁ + + + +n35->n41 + + +d₀ + + + +n46 + +halfar_dynamics_•6:Ω₁ + + + +n35->n46 + + +d₀ + + + +n82 + +Ω₀×ΩC + + + +n35->n82 + + +π₁ + + + +n36 + +halfar_Γ:Ω₁ + + + +n83 + +Ω₁×Ω₁ + + + +n36->n83 + + +π₁ + + + +n37 + +halfar_n:ΩC + + + +n80 + +ΩC×ΩL + + + +n37->n80 + + +π₁ + + + +n88 + +Ω•×ΩC + + + +n37->n88 + + +π₂ + + + +n92 + +Σ2 + + + +n37->n92 + + + + + +n93 + +Σ3 + + + +n37->n93 + + + + + +n38->n65 + + + + + +n39 + +halfar_dynamics_mult_1:Ω• + + + +n85 + +Ω•×Ω• + + + +n39->n85 + + +π₁ + + + +n40 + +halfar_dynamics_mult_2:Ω₁ + + + +n62 + +•_14_1:Ω̃₀ + + + +n40->n62 + + +⋆₁ + + + +n41->n83 + + +π₂ + + + +n42 + +halfar_dynamics_•2:Ω• + + + +n84 + +Ω₁×Ω• + + + +n42->n84 + + +π₂ + + + +n43 + +halfar_dynamics_•3:Ω• + + + +n43->n42 + + +avg₀₁ + + + +n44 + +halfar_dynamics_•4:Ω• + + + +n81 + +Ω•×Ω• + + + +n44->n81 + + +π₁ + + + +n45 + +halfar_dynamics_•5:Ω• + + + +n45->n44 + + +mag + + + +n46->n45 + + + + + + +n47 + +halfar_dynamics_•7:Ω• + + + +n47->n81 + + +π₂ + + + +n48 + +1:ΩL + + + +n48->n80 + + +π₂ + + + +n49 + +halfar_dynamics_•8:Ω• + + + +n49->n85 + + +π₂ + + + +n50 + +halfar_dynamics_•9:Ω₀ + + + +n50->n49 + + +avg₀₁ + + + +n51 + +2:ΩL + + + +n51->n92 + + + + + +n52 + +halfar_dynamics_sum_1:ΩC + + + +n52->n82 + + +π₂ + + + +n53 + +halfar_dynamics_mult_3:Ω₁ + + + +n53->n84 + + +π₁ + + + +n54 + +halfar_stress_ρ:ΩC + + + +n87 + +ΩC×ΩC + + + +n54->n87 + + +π₁ + + + +n55 + +halfar_stress_g:ΩC + + + +n55->n87 + + +π₂ + + + +n56 + +halfar_stress_•1:Ω• + + + +n56->n89 + + +π₁ + + + +n57 + +2:ΩL + + + +n86 + +ΩL×ΩC + + + +n57->n86 + + +π₁ + + + +n57->n93 + + + + + +n58 + +halfar_stress_sum_1:ΩC + + + +n58->n86 + + +π₂ + + + +n59 + +halfar_stress_•2:Ω• + + + +n90 + +Ω•×Ω• + + + +n59->n90 + + +π₂ + + + +n60 + +halfar_stress_•3:Ω• + + + +n60->n88 + + +π₁ + + + +n61 + +halfar_stress_mult_1:Ω• + + + +n61->n90 + + +π₁ + + + +n63 + +•_14_2:Ω̃₁ + + + +n62->n63 + + +dual_d₀ + + + +n63->n38 + + +⋆₀⁻¹ + + + +n64 + + + +n64->n1 + + + + + +n64->n5 + + + + + +n64->n10 + + + + + +n64->n13 + + + + + +n64->n14 + + + + + +n64->n16 + + + + + +n64->n17 + + + + + +n64->n18 + + + + + +n64->n35 + + + + + +n64->n37 + + + + + +n64->n54 + + + + + +n64->n55 + + + + + +n66->n7 + + +- + + + +n67->n6 + + +./ + + + +n68->n12 + + +.- + + + +n69->n2 + + +.* + + + +n70->n15 + + +.* + + + +n71->n3 + + +.+ + + + +n72->n20 + + +./ + + + +n73->n23 + + +.* + + + +n74->n4 + + +.* + + + +n75->n9 + + +* + + + +n76->n31 + + +* + + + +n77->n29 + + +^ + + + +n78->n34 + + +* + + + +n79->n27 + + +* + + + +n80->n47 + + +- + + + +n81->n43 + + +^ + + + +n82->n50 + + +^ + + + +n83->n53 + + +* + + + +n84->n39 + + +* + + + +n85->n40 + + +* + + + +n86->n56 + + +/ + + + +n87->n60 + + +* + + + +n88->n59 + + +^ + + + +n89->n61 + + +* + + + +n90->n36 + + +* + + + +n91->n8 + + ++ + + + +n92->n52 + + ++ + + + +n93->n58 + + ++ + + + diff --git a/dev/budyko_sellers_halfar/8a751216.svg b/dev/budyko_sellers_halfar/8a751216.svg deleted file mode 100644 index 17a77ec7..00000000 --- a/dev/budyko_sellers_halfar/8a751216.svg +++ /dev/null @@ -1,1319 +0,0 @@ - - - - - - -G - - - -n1 - -Tₛ:Ω₀ - - - -n6 - -budyko_sellers_energy_Tₛ̇:Ω₀ - - - -n1->n6 - - -∂ₜ - - - -n19 - -budyko_sellers_diffusion_•1:Ω₁ - - - -n1->n19 - - -d₀ - - - -n68 - -ΩC×Ω₀ - - - -n1->n68 - - -π₂ - - - -n74 - -ΩL×Ω₀ - - - -n1->n74 - - -π₂ - - - -n2 - -budyko_sellers_ASR:Ω₀ - - - -n64 - -Ω₀×Ω₀ - - - -n2->n64 - - -π₁ - - - -n3 - -budyko_sellers_OLR:Ω₀ - - - -n3->n64 - - -π₂ - - - -n4 - -budyko_sellers_HT:Ω₀ - - - -n89 - -Σ1 - - - -n4->n89 - - - - - -n5 - -budyko_sellers_energy_C:ΩC - - - -n65 - -Ω₀×ΩC - - - -n5->n65 - - -π₂ - - - -n63 - - - -n6->n63 - - - - - -n7 - -budyko_sellers_energy_•1:Ω₀ - - - -n7->n89 - - - - - -n8 - -budyko_sellers_energy_sum_1:Ω₀ - - - -n8->n65 - - -π₁ - - - -n9 - -budyko_sellers_Q:Ω₀ - - - -n67 - -Ω₀×Ω₀ - - - -n9->n67 - - -π₂ - - - -n10 - -budyko_sellers_absorbed_radiation_α:ΩC - - - -n66 - -ΩL×ΩC - - - -n10->n66 - - -π₂ - - - -n11 - -1:ΩL - - - -n11->n66 - - -π₁ - - - -n12 - -budyko_sellers_absorbed_radiation_•1:Ω₀ - - - -n12->n67 - - -π₁ - - - -n13 - -budyko_sellers_outgoing_radiation_A:ΩC - - - -n69 - -ΩC×Ω₀ - - - -n13->n69 - - -π₁ - - - -n14 - -budyko_sellers_outgoing_radiation_B:ΩC - - - -n14->n68 - - -π₁ - - - -n15 - -budyko_sellers_outgoing_radiation_•1:Ω₀ - - - -n15->n69 - - -π₂ - - - -n16 - -budyko_sellers_diffusion_D:ΩC - - - -n70 - -ΩC×ΩC - - - -n16->n70 - - -π₁ - - - -n17 - -budyko_sellers_cosϕᵖ:ΩC - - - -n17->n70 - - -π₂ - - - -n73 - -ΩL×ΩC - - - -n17->n73 - - -π₂ - - - -n18 - -budyko_sellers_diffusion_cosϕᵈ:ΩC - - - -n71 - -ΩC×Ω̃₀ - - - -n18->n71 - - -π₁ - - - -n24 - -budyko_sellers_diffusion_•6:Ω̃₀ - - - -n19->n24 - - -⋆₁ - - - -n20 - -budyko_sellers_diffusion_•2:Ω₀ - - - -n72 - -Ω₀×Ω₀ - - - -n20->n72 - - -π₁ - - - -n21 - -budyko_sellers_diffusion_•3:Ω₀ - - - -n21->n72 - - -π₂ - - - -n22 - -budyko_sellers_diffusion_•4:Ω̃₁ - - - -n22->n21 - - -⋆₀⁻¹ - - - -n23 - -budyko_sellers_diffusion_•5:Ω̃₀ - - - -n23->n22 - - -dual_d₀ - - - -n24->n71 - - -π₂ - - - -n25 - -450:ΩL - - - -n25->n73 - - -π₁ - - - -n26 - -A:Ω₁ - - - -n87 - -Ω•×Ω₁ - - - -n26->n87 - - -π₂ - - - -n27 - -warming_mult_1:Ω• - - - -n27->n26 - - -avg₀₁ - - - -n28 - -5.8282:ΩL - - - -n76 - -ΩL×Ω• - - - -n28->n76 - - -π₁ - - - -n29 - -warming_•1:Ω• - - - -n29->n76 - - -π₂ - - - -n30 - -10:ΩL - - - -n75 - -ΩL×Ω• - - - -n30->n75 - - -π₁ - - - -n31 - -warming_•2:Ω• - - - -n31->n75 - - -π₂ - - - -n32 - --0.236:ΩL - - - -n32->n74 - - -π₁ - - - -n33 - -1.65e7:ΩL - - - -n77 - -Ω•×ΩL - - - -n33->n77 - - -π₂ - - - -n34 - -warming_mult_2:Ω• - - - -n34->n77 - - -π₁ - - - -n35 - -halfar_h:Ω₀ - - - -n38 - -halfar_dynamics_ḣ:Ω• - - - -n35->n38 - - -∂ₜ - - - -n41 - -halfar_dynamics_•1:Ω• - - - -n35->n41 - - -d - - - -n46 - -halfar_dynamics_•6:Ω• - - - -n35->n46 - - -d - - - -n80 - -Ω₀×Ω• - - - -n35->n80 - - -π₁ - - - -n36 - -halfar_Γ:Ω₁ - - - -n81 - -Ω₁×Ω• - - - -n36->n81 - - -π₁ - - - -n37 - -halfar_n:ΩC - - - -n78 - -ΩC×ΩL - - - -n37->n78 - - -π₁ - - - -n86 - -Ω•×ΩC - - - -n37->n86 - - -π₂ - - - -n90 - -Σ2 - - - -n37->n90 - - - - - -n91 - -Σ3 - - - -n37->n91 - - - - - -n38->n63 - - - - - -n39 - -halfar_dynamics_mult_1:Ω• - - - -n83 - -Ω•×Ω• - - - -n39->n83 - - -π₁ - - - -n40 - -halfar_dynamics_mult_2:Ω• - - - -n40->n38 - - -⋆⋅d⋅⋆ - - - -n41->n81 - - -π₂ - - - -n42 - -halfar_dynamics_•2:Ω• - - - -n82 - -Ω•×Ω• - - - -n42->n82 - - -π₂ - - - -n43 - -halfar_dynamics_•3:Ω• - - - -n43->n42 - - -avg₀₁ - - - -n44 - -halfar_dynamics_•4:Ω• - - - -n79 - -Ω•×Ω• - - - -n44->n79 - - -π₁ - - - -n45 - -halfar_dynamics_•5:Ω• - - - -n45->n44 - - -mag - - - -n46->n45 - - - - - - -n47 - -halfar_dynamics_•7:Ω• - - - -n47->n79 - - -π₂ - - - -n48 - -1:ΩL - - - -n48->n78 - - -π₂ - - - -n49 - -halfar_dynamics_•8:Ω• - - - -n49->n83 - - -π₂ - - - -n50 - -halfar_dynamics_•9:Ω• - - - -n50->n49 - - -avg₀₁ - - - -n51 - -2:ΩL - - - -n51->n90 - - - - - -n52 - -halfar_dynamics_sum_1:Ω• - - - -n52->n80 - - -π₂ - - - -n53 - -halfar_dynamics_mult_3:Ω• - - - -n53->n82 - - -π₁ - - - -n54 - -halfar_stress_ρ:ΩC - - - -n85 - -ΩC×ΩC - - - -n54->n85 - - -π₁ - - - -n55 - -halfar_stress_g:ΩC - - - -n55->n85 - - -π₂ - - - -n56 - -halfar_stress_•1:Ω• - - - -n56->n87 - - -π₁ - - - -n57 - -2:ΩL - - - -n84 - -ΩL×Ω• - - - -n57->n84 - - -π₁ - - - -n57->n91 - - - - - -n58 - -halfar_stress_sum_1:Ω• - - - -n58->n84 - - -π₂ - - - -n59 - -halfar_stress_•2:Ω• - - - -n88 - -Ω•×Ω• - - - -n59->n88 - - -π₂ - - - -n60 - -halfar_stress_•3:Ω• - - - -n60->n86 - - -π₁ - - - -n61 - -halfar_stress_mult_1:Ω• - - - -n61->n88 - - -π₁ - - - -n62 - - - -n62->n1 - - - - - -n62->n5 - - - - - -n62->n10 - - - - - -n62->n13 - - - - - -n62->n14 - - - - - -n62->n16 - - - - - -n62->n17 - - - - - -n62->n18 - - - - - -n62->n35 - - - - - -n62->n37 - - - - - -n62->n54 - - - - - -n62->n55 - - - - - -n64->n7 - - -- - - - -n65->n6 - - -./ - - - -n66->n12 - - -.- - - - -n67->n2 - - -.* - - - -n68->n15 - - -.* - - - -n69->n3 - - -.+ - - - -n70->n20 - - -./ - - - -n71->n23 - - -.* - - - -n72->n4 - - -.* - - - -n73->n9 - - -* - - - -n74->n31 - - -* - - - -n75->n29 - - -^ - - - -n76->n34 - - -* - - - -n77->n27 - - -* - - - -n78->n47 - - -- - - - -n79->n43 - - -^ - - - -n80->n50 - - -^ - - - -n81->n53 - - -* - - - -n82->n39 - - -* - - - -n83->n40 - - -* - - - -n84->n56 - - -/ - - - -n85->n60 - - -* - - - -n86->n59 - - -^ - - - -n87->n61 - - -* - - - -n88->n36 - - -* - - - -n89->n8 - - -+ - - - -n90->n52 - - -+ - - - -n91->n58 - - -+ - - - diff --git a/dev/budyko_sellers_halfar/8aac0d02.svg b/dev/budyko_sellers_halfar/8aac0d02.svg deleted file mode 100644 index 6ab81da6..00000000 --- a/dev/budyko_sellers_halfar/8aac0d02.svg +++ /dev/null @@ -1,1345 +0,0 @@ - - - - - - -G - - - -n1 - -Tₛ:Ω₀ - - - -n6 - -budyko_sellers_energy_Tₛ̇:Ω₀ - - - -n1->n6 - - -∂ₜ - - - -n19 - -budyko_sellers_diffusion_•1:Ω₁ - - - -n1->n19 - - -d₀ - - - -n70 - -ΩC×Ω₀ - - - -n1->n70 - - -π₂ - - - -n76 - -ΩL×Ω₀ - - - -n1->n76 - - -π₂ - - - -n2 - -budyko_sellers_ASR:Ω₀ - - - -n66 - -Ω₀×Ω₀ - - - -n2->n66 - - -π₁ - - - -n3 - -budyko_sellers_OLR:Ω₀ - - - -n3->n66 - - -π₂ - - - -n4 - -budyko_sellers_HT:Ω₀ - - - -n91 - -Σ1 - - - -n4->n91 - - - - - -n5 - -budyko_sellers_energy_C:ΩC - - - -n67 - -Ω₀×ΩC - - - -n5->n67 - - -π₂ - - - -n65 - - - -n6->n65 - - - - - -n7 - -budyko_sellers_energy_•1:Ω₀ - - - -n7->n91 - - - - - -n8 - -budyko_sellers_energy_sum_1:Ω₀ - - - -n8->n67 - - -π₁ - - - -n9 - -budyko_sellers_Q:Ω₀ - - - -n69 - -Ω₀×Ω₀ - - - -n9->n69 - - -π₂ - - - -n10 - -budyko_sellers_absorbed_radiation_α:ΩC - - - -n68 - -ΩL×ΩC - - - -n10->n68 - - -π₂ - - - -n11 - -1:ΩL - - - -n11->n68 - - -π₁ - - - -n12 - -budyko_sellers_absorbed_radiation_•1:Ω₀ - - - -n12->n69 - - -π₁ - - - -n13 - -budyko_sellers_outgoing_radiation_A:ΩC - - - -n71 - -ΩC×Ω₀ - - - -n13->n71 - - -π₁ - - - -n14 - -budyko_sellers_outgoing_radiation_B:ΩC - - - -n14->n70 - - -π₁ - - - -n15 - -budyko_sellers_outgoing_radiation_•1:Ω₀ - - - -n15->n71 - - -π₂ - - - -n16 - -budyko_sellers_diffusion_D:ΩC - - - -n72 - -ΩC×ΩC - - - -n16->n72 - - -π₁ - - - -n17 - -budyko_sellers_cosϕᵖ:ΩC - - - -n17->n72 - - -π₂ - - - -n75 - -ΩL×ΩC - - - -n17->n75 - - -π₂ - - - -n18 - -budyko_sellers_diffusion_cosϕᵈ:ΩC - - - -n73 - -ΩC×Ω̃₀ - - - -n18->n73 - - -π₁ - - - -n24 - -budyko_sellers_diffusion_•6:Ω̃₀ - - - -n19->n24 - - -⋆₁ - - - -n20 - -budyko_sellers_diffusion_•2:Ω₀ - - - -n74 - -Ω₀×Ω₀ - - - -n20->n74 - - -π₁ - - - -n21 - -budyko_sellers_diffusion_•3:Ω₀ - - - -n21->n74 - - -π₂ - - - -n22 - -budyko_sellers_diffusion_•4:Ω̃₁ - - - -n22->n21 - - -⋆₀⁻¹ - - - -n23 - -budyko_sellers_diffusion_•5:Ω̃₀ - - - -n23->n22 - - -dual_d₀ - - - -n24->n73 - - -π₂ - - - -n25 - -450:ΩL - - - -n25->n75 - - -π₁ - - - -n26 - -A:Ω₁ - - - -n89 - -Ω•×Ω₁ - - - -n26->n89 - - -π₂ - - - -n27 - -warming_mult_1:Ω₀ - - - -n27->n26 - - -avg₀₁ - - - -n28 - -5.8282:ΩL - - - -n78 - -ΩL×Ω₀ - - - -n28->n78 - - -π₁ - - - -n29 - -warming_•1:Ω₀ - - - -n29->n78 - - -π₂ - - - -n30 - -10:ΩL - - - -n77 - -ΩL×Ω₀ - - - -n30->n77 - - -π₁ - - - -n31 - -warming_•2:Ω₀ - - - -n31->n77 - - -π₂ - - - -n32 - --0.236:ΩL - - - -n32->n76 - - -π₁ - - - -n33 - -1.65e7:ΩL - - - -n79 - -Ω₀×ΩL - - - -n33->n79 - - -π₂ - - - -n34 - -warming_mult_2:Ω₀ - - - -n34->n79 - - -π₁ - - - -n35 - -halfar_h:Ω₀ - - - -n38 - -halfar_dynamics_ḣ:Ω₀ - - - -n35->n38 - - -∂ₜ - - - -n41 - -halfar_dynamics_•1:Ω₁ - - - -n35->n41 - - -d₀ - - - -n46 - -halfar_dynamics_•6:Ω₁ - - - -n35->n46 - - -d₀ - - - -n82 - -Ω₀×ΩC - - - -n35->n82 - - -π₁ - - - -n36 - -halfar_Γ:Ω₁ - - - -n83 - -Ω₁×Ω₁ - - - -n36->n83 - - -π₁ - - - -n37 - -halfar_n:ΩC - - - -n80 - -ΩC×ΩL - - - -n37->n80 - - -π₁ - - - -n88 - -Ω•×ΩC - - - -n37->n88 - - -π₂ - - - -n92 - -Σ2 - - - -n37->n92 - - - - - -n93 - -Σ3 - - - -n37->n93 - - - - - -n38->n65 - - - - - -n39 - -halfar_dynamics_mult_1:Ω• - - - -n85 - -Ω•×Ω• - - - -n39->n85 - - -π₁ - - - -n40 - -halfar_dynamics_mult_2:Ω₁ - - - -n62 - -•_14_1:Ω̃₀ - - - -n40->n62 - - -⋆₁ - - - -n41->n83 - - -π₂ - - - -n42 - -halfar_dynamics_•2:Ω• - - - -n84 - -Ω₁×Ω• - - - -n42->n84 - - -π₂ - - - -n43 - -halfar_dynamics_•3:Ω• - - - -n43->n42 - - -avg₀₁ - - - -n44 - -halfar_dynamics_•4:Ω• - - - -n81 - -Ω•×Ω• - - - -n44->n81 - - -π₁ - - - -n45 - -halfar_dynamics_•5:Ω• - - - -n45->n44 - - -mag - - - -n46->n45 - - - - - - -n47 - -halfar_dynamics_•7:Ω• - - - -n47->n81 - - -π₂ - - - -n48 - -1:ΩL - - - -n48->n80 - - -π₂ - - - -n49 - -halfar_dynamics_•8:Ω• - - - -n49->n85 - - -π₂ - - - -n50 - -halfar_dynamics_•9:Ω₀ - - - -n50->n49 - - -avg₀₁ - - - -n51 - -2:ΩL - - - -n51->n92 - - - - - -n52 - -halfar_dynamics_sum_1:ΩC - - - -n52->n82 - - -π₂ - - - -n53 - -halfar_dynamics_mult_3:Ω₁ - - - -n53->n84 - - -π₁ - - - -n54 - -halfar_stress_ρ:ΩC - - - -n87 - -ΩC×ΩC - - - -n54->n87 - - -π₁ - - - -n55 - -halfar_stress_g:ΩC - - - -n55->n87 - - -π₂ - - - -n56 - -halfar_stress_•1:Ω• - - - -n56->n89 - - -π₁ - - - -n57 - -2:ΩL - - - -n86 - -ΩL×ΩC - - - -n57->n86 - - -π₁ - - - -n57->n93 - - - - - -n58 - -halfar_stress_sum_1:ΩC - - - -n58->n86 - - -π₂ - - - -n59 - -halfar_stress_•2:Ω• - - - -n90 - -Ω•×Ω• - - - -n59->n90 - - -π₂ - - - -n60 - -halfar_stress_•3:Ω• - - - -n60->n88 - - -π₁ - - - -n61 - -halfar_stress_mult_1:Ω• - - - -n61->n90 - - -π₁ - - - -n63 - -•_14_2:Ω̃₁ - - - -n62->n63 - - -dual_d₀ - - - -n63->n38 - - -⋆₀⁻¹ - - - -n64 - - - -n64->n1 - - - - - -n64->n5 - - - - - -n64->n10 - - - - - -n64->n13 - - - - - -n64->n14 - - - - - -n64->n16 - - - - - -n64->n17 - - - - - -n64->n18 - - - - - -n64->n35 - - - - - -n64->n37 - - - - - -n64->n54 - - - - - -n64->n55 - - - - - -n66->n7 - - -- - - - -n67->n6 - - -./ - - - -n68->n12 - - -.- - - - -n69->n2 - - -.* - - - -n70->n15 - - -.* - - - -n71->n3 - - -.+ - - - -n72->n20 - - -./ - - - -n73->n23 - - -.* - - - -n74->n4 - - -.* - - - -n75->n9 - - -* - - - -n76->n31 - - -* - - - -n77->n29 - - -^ - - - -n78->n34 - - -* - - - -n79->n27 - - -* - - - -n80->n47 - - -- - - - -n81->n43 - - -^ - - - -n82->n50 - - -^ - - - -n83->n53 - - -* - - - -n84->n39 - - -* - - - -n85->n40 - - -* - - - -n86->n56 - - -/ - - - -n87->n60 - - -* - - - -n88->n59 - - -^ - - - -n89->n61 - - -* - - - -n90->n36 - - -* - - - -n91->n8 - - -+ - - - -n92->n52 - - -+ - - - -n93->n58 - - -+ - - - diff --git a/dev/budyko_sellers_halfar/a09449de.svg b/dev/budyko_sellers_halfar/a09449de.svg deleted file mode 100644 index 5bedceb0..00000000 --- a/dev/budyko_sellers_halfar/a09449de.svg +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - -G - - - -n1 - -Tₛ:Ω₀ - - - -n13 - -ΩL×Ω₀ - - - -n1->n13 - - -π₂ - - - -n2 - -A:Ω₁ - - - -n3 - -mult_1:Ω• - - - -n3->n2 - - -avg₀₁ - - - -n4 - -5.8282:ΩL - - - -n15 - -ΩL×Ω• - - - -n4->n15 - - -π₁ - - - -n5 - -•1:Ω• - - - -n5->n15 - - -π₂ - - - -n6 - -10:ΩL - - - -n14 - -ΩL×Ω• - - - -n6->n14 - - -π₁ - - - -n7 - -•2:Ω• - - - -n7->n14 - - -π₂ - - - -n8 - --0.236:ΩL - - - -n8->n13 - - -π₁ - - - -n9 - -1.65e7:ΩL - - - -n16 - -Ω•×ΩL - - - -n9->n16 - - -π₂ - - - -n10 - -mult_2:Ω• - - - -n10->n16 - - -π₁ - - - -n11 - - - -n11->n1 - - - - - -n12 - - - -n13->n7 - - -* - - - -n14->n5 - - -^ - - - -n15->n10 - - -* - - - -n16->n3 - - -* - - - diff --git a/dev/budyko_sellers_halfar/e5e14bac.png b/dev/budyko_sellers_halfar/e5e14bac.png deleted file mode 100644 index 1ff57d55..00000000 Binary files a/dev/budyko_sellers_halfar/e5e14bac.png and /dev/null differ diff --git a/dev/budyko_sellers_halfar/e7fb619b.png b/dev/budyko_sellers_halfar/e7fb619b.png deleted file mode 100644 index b9d290dc..00000000 Binary files a/dev/budyko_sellers_halfar/e7fb619b.png and /dev/null differ diff --git a/dev/budyko_sellers_halfar/fc853150.svg b/dev/budyko_sellers_halfar/fc853150.svg deleted file mode 100644 index 2f203247..00000000 --- a/dev/budyko_sellers_halfar/fc853150.svg +++ /dev/null @@ -1,628 +0,0 @@ - - - - - - -G - - - -n1 - -h:Ω₀ - - - -n4 - -dynamics_ḣ:Ω• - - - -n1->n4 - - -∂ₜ - - - -n7 - -dynamics_•1:Ω• - - - -n1->n7 - - -d - - - -n12 - -dynamics_•6:Ω• - - - -n1->n12 - - -d - - - -n33 - -Ω₀×Ω• - - - -n1->n33 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n34 - -Ω₁×Ω• - - - -n2->n34 - - -π₁ - - - -n3 - -n:ΩC - - - -n31 - -ΩC×ΩL - - - -n3->n31 - - -π₁ - - - -n39 - -Ω•×ΩC - - - -n3->n39 - - -π₂ - - - -n42 - -Σ1 - - - -n3->n42 - - - - - -n43 - -Σ2 - - - -n3->n43 - - - - - -n30 - - - -n4->n30 - - - - - -n5 - -dynamics_mult_1:Ω• - - - -n36 - -Ω•×Ω• - - - -n5->n36 - - -π₁ - - - -n6 - -dynamics_mult_2:Ω• - - - -n6->n4 - - -⋆⋅d⋅⋆ - - - -n7->n34 - - -π₂ - - - -n8 - -dynamics_•2:Ω• - - - -n35 - -Ω•×Ω• - - - -n8->n35 - - -π₂ - - - -n9 - -dynamics_•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -dynamics_•4:Ω• - - - -n32 - -Ω•×Ω• - - - -n10->n32 - - -π₁ - - - -n11 - -dynamics_•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -dynamics_•7:Ω• - - - -n13->n32 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n31 - - -π₂ - - - -n15 - -dynamics_•8:Ω• - - - -n15->n36 - - -π₂ - - - -n16 - -dynamics_•9:Ω• - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n42 - - - - - -n18 - -dynamics_sum_1:Ω• - - - -n18->n33 - - -π₂ - - - -n19 - -dynamics_mult_3:Ω• - - - -n19->n35 - - -π₁ - - - -n20 - -stress_A:Ω₁ - - - -n40 - -Ω•×Ω₁ - - - -n20->n40 - - -π₂ - - - -n21 - -stress_ρ:ΩC - - - -n38 - -ΩC×ΩC - - - -n21->n38 - - -π₁ - - - -n22 - -stress_g:ΩC - - - -n22->n38 - - -π₂ - - - -n23 - -stress_•1:Ω• - - - -n23->n40 - - -π₁ - - - -n24 - -2:ΩL - - - -n37 - -ΩL×Ω• - - - -n24->n37 - - -π₁ - - - -n24->n43 - - - - - -n25 - -stress_sum_1:Ω• - - - -n25->n37 - - -π₂ - - - -n26 - -stress_•2:Ω• - - - -n41 - -Ω•×Ω• - - - -n26->n41 - - -π₂ - - - -n27 - -stress_•3:Ω• - - - -n27->n39 - - -π₁ - - - -n28 - -stress_mult_1:Ω• - - - -n28->n41 - - -π₁ - - - -n29 - - - -n29->n1 - - - - - -n29->n3 - - - - - -n29->n20 - - - - - -n29->n21 - - - - - -n29->n22 - - - - - -n31->n13 - - -- - - - -n32->n9 - - -^ - - - -n33->n16 - - -^ - - - -n34->n19 - - -* - - - -n35->n5 - - -* - - - -n36->n6 - - -* - - - -n37->n23 - - -/ - - - -n38->n27 - - -* - - - -n39->n26 - - -^ - - - -n40->n28 - - -* - - - -n41->n2 - - -* - - - -n42->n18 - - -+ - - - -n43->n25 - - -+ - - - diff --git a/dev/budyko_sellers_halfar/index.html b/dev/budyko_sellers_halfar/index.html index fc6b0744..840a96e2 100644 --- a/dev/budyko_sellers_halfar/index.html +++ b/dev/budyko_sellers_halfar/index.html @@ -16,7 +16,7 @@ using CairoMakie using GeometryBasics: Point2 Point2D = Point2{Float64};
Point2{Float64} (alias for Point{2, Float64})

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.

budyko_sellers = apex(budyko_sellers_cospan)
-halfar = apex(ice_dynamics_cospan)
true

Budyko-Sellers

to_graphviz(budyko_sellers)
Example block output

Halfar

to_graphviz(halfar)
Example block output

Warming

This is a formula that computes A for use in the Halfar glacial dynamics, given T from the Budyko-Sellers model.

# Tₛ(ϕ,t) := Surface temperature
+halfar = apex(ice_dynamics_cospan)
true

Budyko-Sellers

to_graphviz(budyko_sellers)
Example block output

Halfar

to_graphviz(halfar)
Example block output

Warming

This is a formula that computes A for use in the Halfar glacial dynamics, given T from the Budyko-Sellers model.

# Tₛ(ϕ,t) := Surface temperature
 # A(ϕ) := Longwave emissions at 0°C
 warming = @decapode begin
   (Tₛ)::Form0
@@ -25,7 +25,7 @@
   A == avg₀₁(5.8282*10^(-0.236 * Tₛ)*1.65e7)
 
 end
-to_graphviz(warming)
Example block output

Composition

Observe that this composition technique is the same as that used in composing each of the Budyko-Sellers and Halfar models.

budyko_sellers_halfar_composition_diagram = @relation () begin
+to_graphviz(warming)
Example block output

Composition

Observe that this composition technique is the same as that used in composing each of the Budyko-Sellers and Halfar models.

budyko_sellers_halfar_composition_diagram = @relation () begin
   budyko_sellers(Tₛ)
 
   warming(A, Tₛ)
@@ -35,65 +35,65 @@
 to_graphviz(budyko_sellers_halfar_composition_diagram, box_labels=:name, junction_labels=:variable, prog="circo")
- - - + + G - + n1 - -dynamics + +dynamics n3 - -Γ + +Γ n1&%2345;&%2345;n3 - + n4 - -n + +n n1&%2345;&%2345;n4 - + n2 - -stress + +stress n2&%2345;&%2345;n3 - + n2&%2345;&%2345;n4 - + @@ -112,19 +112,21 @@ Open(glens_law, [:Γ,:n])]) ice_dynamics = apex(ice_dynamics_cospan) -to_graphviz(ice_dynamics, verbose=false)Example block output

We have a representation of our composed physics. Now, we need to specify that these dynamics occur in 2 dimensions.

Providing a Semantics

# Interpret this multiphysics diagram in the 2D exterior calculus.
+to_graphviz(ice_dynamics, verbose=false)
Example block output

We have a representation of our composed physics. Now, we need to specify that these dynamics occur in 2 dimensions.

Providing a Semantics

# Interpret this multiphysics diagram in the 2D exterior calculus.
 
 ice_dynamics2D = expand_operators(ice_dynamics)
 infer_types!(ice_dynamics2D)
 resolve_overloads!(ice_dynamics2D)
-to_graphviz(ice_dynamics2D, verbose=false)
Example block output

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.

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.

s′ = triangulated_grid(60_000,100_000,2_000,2_000,Point3D)
+to_graphviz(ice_dynamics2D, verbose=false)
Example block output

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.

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.

s′ = triangulated_grid(60_000,100_000,2_000,2_000,Point3D)
 s = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′)
 subdivide_duals!(s, Barycenter())
 x̄ = mean(p -> p[1], point(s))
 ȳ = mean(p -> p[2], point(s))
 
-wf = wireframe(s)
-save("ice_mesh.png", wf)
CairoMakie.Screen{IMAGE}
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1])
+wf = wireframe!(ax, s)
+save("ice_mesh.png", fig)
CairoMakie.Screen{IMAGE}
 

"Wireframe of the Domain"

Defining input data

We replicate the initial conditions and parameter values used in the CISM benchmark.

# These are the initial conditions to the Halfar Dome test case that the
 # Community Ice Sheet Model uses.
 R₀ = 60_000 * sqrt(0.125)
@@ -155,7 +157,9 @@
 # Set the initial conditions for ice sheet height:
 # Ice height is a primal 0-form. i.e. valued at vertices.
 h₀ = map(x -> height_at_p(x[1], x[2], 0), point(s′))
-fig = mesh(s′, color=h₀, colormap=:jet)
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1])
+msh = mesh!(ax, s′, color=h₀, colormap=:jet)
 save("ice_initial_conditions.png", fig)
CairoMakie.Screen{IMAGE}
 

"Initial Conditions"

# Store these values to be passed to the solver.
 u₀ = ComponentArray(dynamics_h = h₀)
@@ -210,14 +214,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: 267 samples with 1 evaluation.
- Range (minmax):  17.546 ms29.131 ms   GC (min … max): 0.00% … 5.59%
- Time  (median):     18.032 ms               GC (median):    0.00%
- Time  (mean ± σ):   18.645 ms ±  1.629 ms   GC (mean ± σ):  2.31% ± 4.15%
+c = run(b)
BenchmarkTools.Trial: 316 samples with 1 evaluation.
+ Range (minmax):  14.717 ms23.437 ms   GC (min … max): 0.00% … 31.80%
+ Time  (median):     14.937 ms               GC (median):    0.00%
+ Time  (mean ± σ):   15.769 ms ±  1.826 ms   GC (mean ± σ):  3.06% ±  6.22%
 
-   ▄█         ▃▁                                              
-  ▇███▅▅▁▁▇▇███▆▇▄▁▆▁▁▁▁▁▁▁▁▁▄▁▁▁▄▁▁▁▁▁▁▁▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▄▁▄ ▅
-  17.5 ms      Histogram: log(frequency) by time      27.5 ms <
+  █▆               ▁                                    
+  ███▇▇▅▁▁▁▁▁▁▁▁▁▄▁▁▁▅▅▇█▅▇▆▅▅▄▁▁▁▁▁▁▁▄▄▆▅▆▇▅▄▁▆▁▁▁▄▆▄▄▅▁▁▄ ▆
+  14.7 ms      Histogram: log(frequency) by time      21.6 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
@@ -263,19 +267,21 @@
 

"Numeric Solution - Analytic Solution"

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.

# Compute max absolute error:
 hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s′))
 h_diff = soln(tₑ).dynamics_h - hₐ
-maximum(abs.(h_diff))
89.25883061368135

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.

# Compute RMSE not considering the "outside".
+maximum(abs.(h_diff))
89.2588306136831

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.

# Compute RMSE not considering the "outside".
 hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s′))
 nonzeros = findall(!=(0), hₐ)
 h_diff = soln(tₑ).dynamics_h - hₐ
-rmse = sqrt(sum(map(x -> x*x, h_diff[nonzeros])) / length(nonzeros))
10.399805753564129
# Compute RMSE of the entire domain.
+rmse = sqrt(sum(map(x -> x*x, h_diff[nonzeros])) / length(nonzeros))
10.399805753563342
# Compute RMSE of the entire domain.
 hₐ = map(x -> height_at_p(x[1], x[2], 200.0), point(s′))
 h_diff = soln(tₑ).dynamics_h - hₐ
-rmse = sqrt(sum(map(x -> x*x, h_diff)) / length(h_diff))
10.084315213043197
# Create a gif
+rmse = sqrt(sum(map(x -> x*x, h_diff)) / length(h_diff))
10.084315213043634
# Create a gif
 begin
   frames = 100
-  fig, ax, ob = mesh(s′, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(tₑ).dynamics_h))
-  Colorbar(fig[1,2], ob)
+  fig = Figure()
+  ax = CairoMakie.Axis(fig[1,1])
+  msh = mesh!(ax, s′, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(tₑ).dynamics_h))
+  Colorbar(fig[1,2], msh)
   record(fig, "ice_dynamics_cism.gif", range(0.0, tₑ; length=frames); framerate = 30) do t
-    ob.color = soln(t).dynamics_h
+    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/diff_adv.gif b/dev/diff_adv.gif index 502f54f9..1f2f15ca 100644 Binary files a/dev/diff_adv.gif and b/dev/diff_adv.gif differ diff --git a/dev/diff_adv_right.gif b/dev/diff_adv_right.gif index e50836b5..d0e8c764 100644 Binary files a/dev/diff_adv_right.gif and b/dev/diff_adv_right.gif differ diff --git a/dev/diffusion.gif b/dev/diffusion.gif index 62ef455c..cbcb5eb7 100644 Binary files a/dev/diffusion.gif and b/dev/diffusion.gif differ diff --git a/dev/equations/index.html b/dev/equations/index.html index 4634fe7e..dd6b8596 100644 --- a/dev/equations/index.html +++ b/dev/equations/index.html @@ -102,58 +102,58 @@

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.

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.

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 Ẋ̇.

to_graphviz(oscillator)
- - - + + G - + n1 - -X:Ω₀ + +X:Ω₀ n2 - -V:Ω₀ + +V:Ω₀ n1&%2345;>n2 - - -∂ₜ + + +∂ₜ n8 - -Ω•×Ω₀ + +Ω•×Ω₀ n1&%2345;>n8 - - -π₂ + + +π₂ n5 - -V̇:Ω• + +V̇:Ω• n2&%2345;>n5 - - -∂ₜ + + +∂ₜ @@ -368,40 +368,40 @@ n2&%2345;>n7 - - + + n3 - -k:ΩC + +k:ΩC n4 - -•1:Ω• + +•1:Ω• n3&%2345;>n4 - - -&%2345; + + +&%2345; n4&%2345;>n8 - - -π₁ + + +π₁ n5&%2345;>n7 - - + + @@ -410,21 +410,21 @@ n6&%2345;>n1 - - + + n6&%2345;>n3 - - + + n8&%2345;>n5 - - -* + + +* @@ -553,58 +553,58 @@ to_graphviz(oscillator) - - - + + G - + n1 - -X:Ω₀ + +X:Ω₀ n2 - -V:Ω₀ + +V:Ω₀ n1&%2345;>n2 - - -∂ₜ + + +∂ₜ n10 - -Ω₀×Ω₀ + +Ω₀×Ω₀ n1&%2345;>n10 - - -π₂ + + +π₂ n5 - -V̇:Ω₀ + +V̇:Ω₀ n2&%2345;>n5 - - -∂ₜ + + +∂ₜ @@ -884,59 +884,59 @@ n2&%2345;>n8 - - + + n3 - -k:ΩP + +k:ΩP n9 - -ΩL×ΩP + +ΩL×ΩP n3&%2345;>n9 - - -π₂ + + +π₂ n4 - -mult_1:Ω₀ + +mult_1:Ω₀ n4&%2345;>n10 - - -π₁ + + +π₁ n5&%2345;>n8 - - + + n6 - -&%2345;1:ΩL + +&%2345;1:ΩL n6&%2345;>n9 - - -π₁ + + +π₁ @@ -945,29 +945,29 @@ n7&%2345;>n1 - - + + n7&%2345;>n3 - - + + n9&%2345;>n4 - - -* + + +* n10&%2345;>n5 - - -* + + +* -'/>

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.gif b/dev/grigoriev.gif index 00ccce87..c6fe0056 100644 Binary files a/dev/grigoriev.gif and b/dev/grigoriev.gif differ diff --git a/dev/grigoriev.jld2 b/dev/grigoriev.jld2 index 27811934..50f7b787 100644 Binary files a/dev/grigoriev.jld2 and b/dev/grigoriev.jld2 differ diff --git a/dev/grigoriev/5d5208d9.svg b/dev/grigoriev/5d5208d9.svg new file mode 100644 index 00000000..562dfccb --- /dev/null +++ b/dev/grigoriev/5d5208d9.svg @@ -0,0 +1,628 @@ + + + + + + +G + + + +n1 + +h:Ω₀ + + + +n4 + +dynamics_ḣ:Ω• + + + +n1->n4 + + +∂ₜ + + + +n7 + +dynamics_•1:Ω• + + + +n1->n7 + + +d + + + +n12 + +dynamics_•6:Ω• + + + +n1->n12 + + +d + + + +n33 + +Ω₀×Ω• + + + +n1->n33 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n34 + +Ω₁×Ω• + + + +n2->n34 + + +π₁ + + + +n3 + +n:ΩC + + + +n31 + +ΩC×ΩL + + + +n3->n31 + + +π₁ + + + +n39 + +Ω•×ΩC + + + +n3->n39 + + +π₂ + + + +n42 + +Σ1 + + + +n3->n42 + + + + + +n43 + +Σ2 + + + +n3->n43 + + + + + +n30 + + + +n4->n30 + + + + + +n5 + +dynamics_mult_1:Ω• + + + +n36 + +Ω•×Ω• + + + +n5->n36 + + +π₁ + + + +n6 + +dynamics_mult_2:Ω• + + + +n6->n4 + + +⋆⋅d⋅⋆ + + + +n7->n34 + + +π₂ + + + +n8 + +dynamics_•2:Ω• + + + +n35 + +Ω•×Ω• + + + +n8->n35 + + +π₂ + + + +n9 + +dynamics_•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +dynamics_•4:Ω• + + + +n32 + +Ω•×Ω• + + + +n10->n32 + + +π₁ + + + +n11 + +dynamics_•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +dynamics_•7:Ω• + + + +n13->n32 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n31 + + +π₂ + + + +n15 + +dynamics_•8:Ω• + + + +n15->n36 + + +π₂ + + + +n16 + +dynamics_•9:Ω• + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n42 + + + + + +n18 + +dynamics_sum_1:Ω• + + + +n18->n33 + + +π₂ + + + +n19 + +dynamics_mult_3:Ω• + + + +n19->n35 + + +π₁ + + + +n20 + +stress_A:ΩC + + + +n40 + +Ω•×ΩC + + + +n20->n40 + + +π₂ + + + +n21 + +stress_ρ:ΩC + + + +n38 + +ΩC×ΩC + + + +n21->n38 + + +π₁ + + + +n22 + +stress_g:ΩC + + + +n22->n38 + + +π₂ + + + +n23 + +stress_•1:Ω• + + + +n23->n40 + + +π₁ + + + +n24 + +2:ΩL + + + +n37 + +ΩL×Ω• + + + +n24->n37 + + +π₁ + + + +n24->n43 + + + + + +n25 + +stress_sum_1:Ω• + + + +n25->n37 + + +π₂ + + + +n26 + +stress_•2:Ω• + + + +n41 + +Ω•×Ω• + + + +n26->n41 + + +π₂ + + + +n27 + +stress_•3:Ω• + + + +n27->n39 + + +π₁ + + + +n28 + +stress_mult_1:Ω• + + + +n28->n41 + + +π₁ + + + +n29 + + + +n29->n1 + + + + + +n29->n3 + + + + + +n29->n20 + + + + + +n29->n21 + + + + + +n29->n22 + + + + + +n31->n13 + + +- + + + +n32->n9 + + +^ + + + +n33->n16 + + +^ + + + +n34->n19 + + +* + + + +n35->n5 + + +* + + + +n36->n6 + + +* + + + +n37->n23 + + +/ + + + +n38->n27 + + +* + + + +n39->n26 + + +^ + + + +n40->n28 + + +* + + + +n41->n2 + + +* + + + +n42->n18 + + ++ + + + +n43->n25 + + ++ + + + diff --git a/dev/grigoriev/7fd23918.svg b/dev/grigoriev/7fd23918.svg deleted file mode 100644 index 7c734cc7..00000000 --- a/dev/grigoriev/7fd23918.svg +++ /dev/null @@ -1,628 +0,0 @@ - - - - - - -G - - - -n1 - -h:Ω₀ - - - -n4 - -dynamics_ḣ:Ω• - - - -n1->n4 - - -∂ₜ - - - -n7 - -dynamics_•1:Ω• - - - -n1->n7 - - -d - - - -n12 - -dynamics_•6:Ω• - - - -n1->n12 - - -d - - - -n33 - -Ω₀×Ω• - - - -n1->n33 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n34 - -Ω₁×Ω• - - - -n2->n34 - - -π₁ - - - -n3 - -n:ΩC - - - -n31 - -ΩC×ΩL - - - -n3->n31 - - -π₁ - - - -n39 - -Ω•×ΩC - - - -n3->n39 - - -π₂ - - - -n42 - -Σ1 - - - -n3->n42 - - - - - -n43 - -Σ2 - - - -n3->n43 - - - - - -n30 - - - -n4->n30 - - - - - -n5 - -dynamics_mult_1:Ω• - - - -n36 - -Ω•×Ω• - - - -n5->n36 - - -π₁ - - - -n6 - -dynamics_mult_2:Ω• - - - -n6->n4 - - -⋆⋅d⋅⋆ - - - -n7->n34 - - -π₂ - - - -n8 - -dynamics_•2:Ω• - - - -n35 - -Ω•×Ω• - - - -n8->n35 - - -π₂ - - - -n9 - -dynamics_•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -dynamics_•4:Ω• - - - -n32 - -Ω•×Ω• - - - -n10->n32 - - -π₁ - - - -n11 - -dynamics_•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -dynamics_•7:Ω• - - - -n13->n32 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n31 - - -π₂ - - - -n15 - -dynamics_•8:Ω• - - - -n15->n36 - - -π₂ - - - -n16 - -dynamics_•9:Ω• - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n42 - - - - - -n18 - -dynamics_sum_1:Ω• - - - -n18->n33 - - -π₂ - - - -n19 - -dynamics_mult_3:Ω• - - - -n19->n35 - - -π₁ - - - -n20 - -stress_A:ΩC - - - -n40 - -Ω•×ΩC - - - -n20->n40 - - -π₂ - - - -n21 - -stress_ρ:ΩC - - - -n38 - -ΩC×ΩC - - - -n21->n38 - - -π₁ - - - -n22 - -stress_g:ΩC - - - -n22->n38 - - -π₂ - - - -n23 - -stress_•1:Ω• - - - -n23->n40 - - -π₁ - - - -n24 - -2:ΩL - - - -n37 - -ΩL×Ω• - - - -n24->n37 - - -π₁ - - - -n24->n43 - - - - - -n25 - -stress_sum_1:Ω• - - - -n25->n37 - - -π₂ - - - -n26 - -stress_•2:Ω• - - - -n41 - -Ω•×Ω• - - - -n26->n41 - - -π₂ - - - -n27 - -stress_•3:Ω• - - - -n27->n39 - - -π₁ - - - -n28 - -stress_mult_1:Ω• - - - -n28->n41 - - -π₁ - - - -n29 - - - -n29->n1 - - - - - -n29->n3 - - - - - -n29->n20 - - - - - -n29->n21 - - - - - -n29->n22 - - - - - -n31->n13 - - -- - - - -n32->n9 - - -^ - - - -n33->n16 - - -^ - - - -n34->n19 - - -* - - - -n35->n5 - - -* - - - -n36->n6 - - -* - - - -n37->n23 - - -/ - - - -n38->n27 - - -* - - - -n39->n26 - - -^ - - - -n40->n28 - - -* - - - -n41->n2 - - -* - - - -n42->n18 - - -+ - - - -n43->n25 - - -+ - - - diff --git a/dev/grigoriev/cfc97c5c.png b/dev/grigoriev/cfc97c5c.png deleted file mode 100644 index 483d57e1..00000000 Binary files a/dev/grigoriev/cfc97c5c.png and /dev/null differ diff --git a/dev/grigoriev/index.html b/dev/grigoriev/index.html index 7fca1622..1cf5fb2a 100644 --- a/dev/grigoriev/index.html +++ b/dev/grigoriev/index.html @@ -59,7 +59,12 @@ s′[:point] = map(x -> x + Point3D(MIN_X, MIN_Y, 0), s′[:point]) s = EmbeddedDeltaDualComplex2D{Bool, Float64, Point3D}(s′) subdivide_duals!(s, Barycenter()) -wireframe(s)Example block output

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:

# These are the values used by the CISM benchmark:
+
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1])
+wf = wireframe!(ax, s)
+display(fig)
CairoMakie.Screen{IMAGE}
+

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:

# These are the values used by the CISM benchmark:
 n = 3
 ρ = 910
 g = 9.8101
@@ -103,7 +108,7 @@
   Open(glens_law, [:Γ,:n])])
 
 ice_dynamics = apex(ice_dynamics_cospan)
-to_graphviz(ice_dynamics)
Example block output

Define our functions

include("sharp_op.jl")
+to_graphviz(ice_dynamics)
Example block output

Define our functions

include("sharp_op.jl")
 function generate(sd, my_symbol; hodge=GeometricHodge())
   ♯_m = ♯_mat(sd)
   I = Vector{Int64}()
@@ -177,13 +182,15 @@
 function save_dynamics(save_file_name)
   time = Observable(0.0)
   h = @lift(soln($time).h)
-  f,a,o = mesh(s′, color=h, colormap=:jet,
-             colorrange=extrema(soln(tₑ).h);
-             axis = (; title = @lift("Grigoriev Ice Cap Dynamic Thickness [m] at time $($time))")))
-  Colorbar(f[1,2], limits=extrema(soln(0.0).h), colormap=:jet)
+  f = Figure()
+  ax = CairoMakie.Axis(f[1,1], title = @lift("Grigoriev Ice Cap Dynamic Thickness [m] at time $($time)"))
+  gmsh = mesh!(ax, s′, color=h, colormap=:jet,
+               colorrange=extrema(soln(tₑ).h))
+  #Colorbar(f[1,2], gmsh, limits=extrema(soln(tₑ).h))
+  Colorbar(f[1,2], gmsh)
   timestamps = range(0, tₑ, step=1e-1)
   record(f, save_file_name, timestamps; framerate = 15) do t
     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/grigoriev_fc.png b/dev/grigoriev_fc.png index 60ffcf7f..2c61be27 100644 Binary files a/dev/grigoriev_fc.png and b/dev/grigoriev_fc.png differ diff --git a/dev/grigoriev_ic.png b/dev/grigoriev_ic.png index 33bd6b08..30016580 100644 Binary files a/dev/grigoriev_ic.png and b/dev/grigoriev_ic.png differ diff --git a/dev/ice_analytic_solution.png b/dev/ice_analytic_solution.png index 4a5e80fb..4fc3fd7b 100644 Binary files a/dev/ice_analytic_solution.png and b/dev/ice_analytic_solution.png differ diff --git a/dev/ice_dynamics.gif b/dev/ice_dynamics.gif index 2520f05d..f544bf6e 100644 Binary files a/dev/ice_dynamics.gif and b/dev/ice_dynamics.gif differ diff --git a/dev/ice_dynamics/03586779.svg b/dev/ice_dynamics/03586779.svg deleted file mode 100644 index cf4f1f46..00000000 --- a/dev/ice_dynamics/03586779.svg +++ /dev/null @@ -1,654 +0,0 @@ - - - - - - -G - - - -n1 - -dynamics_h:Ω₀ - - - -n4 - -dynamics_ḣ:Ω₀ - - - -n1->n4 - - -∂ₜ - - - -n7 - -dynamics_•1:Ω₁ - - - -n1->n7 - - -d₀ - - - -n12 - -dynamics_•6:Ω₁ - - - -n1->n12 - - -d₀ - - - -n35 - -Ω₀×ΩC - - - -n1->n35 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n36 - -Ω₁×Ω₁ - - - -n2->n36 - - -π₁ - - - -n3 - -n:ΩC - - - -n33 - -ΩC×ΩL - - - -n3->n33 - - -π₁ - - - -n41 - -Ω•×ΩC - - - -n3->n41 - - -π₂ - - - -n44 - -Σ1 - - - -n3->n44 - - - - - -n45 - -Σ2 - - - -n3->n45 - - - - - -n32 - - - -n4->n32 - - - - - -n5 - -dynamics_mult_1:Ω• - - - -n38 - -Ω•×Ω• - - - -n5->n38 - - -π₁ - - - -n6 - -dynamics_mult_2:Ω₁ - - - -n29 - -•_8_1:Ω̃₀ - - - -n6->n29 - - -⋆₁ - - - -n7->n36 - - -π₂ - - - -n8 - -dynamics_•2:Ω• - - - -n37 - -Ω₁×Ω• - - - -n8->n37 - - -π₂ - - - -n9 - -dynamics_•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -dynamics_•4:Ω• - - - -n34 - -Ω•×Ω• - - - -n10->n34 - - -π₁ - - - -n11 - -dynamics_•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -dynamics_•7:Ω• - - - -n13->n34 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n33 - - -π₂ - - - -n15 - -dynamics_•8:Ω• - - - -n15->n38 - - -π₂ - - - -n16 - -dynamics_•9:Ω₀ - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n44 - - - - - -n18 - -dynamics_sum_1:ΩC - - - -n18->n35 - - -π₂ - - - -n19 - -dynamics_mult_3:Ω₁ - - - -n19->n37 - - -π₁ - - - -n20 - -stress_A:ΩC - - - -n42 - -Ω•×ΩC - - - -n20->n42 - - -π₂ - - - -n21 - -stress_ρ:ΩC - - - -n40 - -ΩC×ΩC - - - -n21->n40 - - -π₁ - - - -n22 - -stress_g:ΩC - - - -n22->n40 - - -π₂ - - - -n23 - -stress_•1:Ω• - - - -n23->n42 - - -π₁ - - - -n24 - -2:ΩL - - - -n39 - -ΩL×ΩC - - - -n24->n39 - - -π₁ - - - -n24->n45 - - - - - -n25 - -stress_sum_1:ΩC - - - -n25->n39 - - -π₂ - - - -n26 - -stress_•2:Ω• - - - -n43 - -Ω•×Ω• - - - -n26->n43 - - -π₂ - - - -n27 - -stress_•3:Ω• - - - -n27->n41 - - -π₁ - - - -n28 - -stress_mult_1:Ω• - - - -n28->n43 - - -π₁ - - - -n30 - -•_8_2:Ω̃₁ - - - -n29->n30 - - -dual_d₀ - - - -n30->n4 - - -⋆₀⁻¹ - - - -n31 - - - -n31->n1 - - - - - -n31->n3 - - - - - -n31->n20 - - - - - -n31->n21 - - - - - -n31->n22 - - - - - -n33->n13 - - -- - - - -n34->n9 - - -^ - - - -n35->n16 - - -^ - - - -n36->n19 - - -* - - - -n37->n5 - - -* - - - -n38->n6 - - -* - - - -n39->n23 - - -/ - - - -n40->n27 - - -* - - - -n41->n26 - - -^ - - - -n42->n28 - - -* - - - -n43->n2 - - -* - - - -n44->n18 - - -+ - - - -n45->n25 - - -+ - - - diff --git a/dev/ice_dynamics/0938e770.svg b/dev/ice_dynamics/0938e770.svg new file mode 100644 index 00000000..b61babd2 --- /dev/null +++ b/dev/ice_dynamics/0938e770.svg @@ -0,0 +1,271 @@ + + + + + + +G + + + +n1 + +Γ:Ω₁ + + + +n2 + +A:ΩC + + + +n17 + +Ω•×ΩC + + + +n2->n17 + + +π₂ + + + +n3 + +ρ:ΩC + + + +n15 + +ΩC×ΩC + + + +n3->n15 + + +π₁ + + + +n4 + +g:ΩC + + + +n4->n15 + + +π₂ + + + +n5 + +n:ΩC + + + +n16 + +Ω•×ΩC + + + +n5->n16 + + +π₂ + + + +n19 + +Σ1 + + + +n5->n19 + + + + + +n6 + +•1:Ω• + + + +n6->n17 + + +π₁ + + + +n7 + +2:ΩL + + + +n14 + +ΩL×Ω• + + + +n7->n14 + + +π₁ + + + +n7->n19 + + + + + +n8 + +sum_1:Ω• + + + +n8->n14 + + +π₂ + + + +n9 + +•2:Ω• + + + +n18 + +Ω•×Ω• + + + +n9->n18 + + +π₂ + + + +n10 + +•3:Ω• + + + +n10->n16 + + +π₁ + + + +n11 + +mult_1:Ω• + + + +n11->n18 + + +π₁ + + + +n12 + + + +n12->n2 + + + + + +n12->n3 + + + + + +n12->n4 + + + + + +n12->n5 + + + + + +n13 + + + +n14->n6 + + +/ + + + +n15->n10 + + +* + + + +n16->n9 + + +^ + + + +n17->n11 + + +* + + + +n18->n1 + + +* + + + +n19->n8 + + ++ + + + diff --git a/dev/ice_dynamics/23f87477.svg b/dev/ice_dynamics/23f87477.svg deleted file mode 100644 index 1ccf3c29..00000000 --- a/dev/ice_dynamics/23f87477.svg +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - -G - - - -n1 - -Γ:Ω₁ - - - -n2 - -A:ΩC - - - -n17 - -Ω•×ΩC - - - -n2->n17 - - -π₂ - - - -n3 - -ρ:ΩC - - - -n15 - -ΩC×ΩC - - - -n3->n15 - - -π₁ - - - -n4 - -g:ΩC - - - -n4->n15 - - -π₂ - - - -n5 - -n:ΩC - - - -n16 - -Ω•×ΩC - - - -n5->n16 - - -π₂ - - - -n19 - -Σ1 - - - -n5->n19 - - - - - -n6 - -•1:Ω• - - - -n6->n17 - - -π₁ - - - -n7 - -2:ΩL - - - -n14 - -ΩL×Ω• - - - -n7->n14 - - -π₁ - - - -n7->n19 - - - - - -n8 - -sum_1:Ω• - - - -n8->n14 - - -π₂ - - - -n9 - -•2:Ω• - - - -n18 - -Ω•×Ω• - - - -n9->n18 - - -π₂ - - - -n10 - -•3:Ω• - - - -n10->n16 - - -π₁ - - - -n11 - -mult_1:Ω• - - - -n11->n18 - - -π₁ - - - -n12 - - - -n12->n2 - - - - - -n12->n3 - - - - - -n12->n4 - - - - - -n12->n5 - - - - - -n13 - - - -n14->n6 - - -/ - - - -n15->n10 - - -* - - - -n16->n9 - - -^ - - - -n17->n11 - - -* - - - -n18->n1 - - -* - - - -n19->n8 - - -+ - - - diff --git a/dev/ice_dynamics/39586556.svg b/dev/ice_dynamics/39586556.svg new file mode 100644 index 00000000..a98b0bbf --- /dev/null +++ b/dev/ice_dynamics/39586556.svg @@ -0,0 +1,654 @@ + + + + + + +G + + + +n1 + +dynamics_h:Ω₀ + + + +n4 + +dynamics_ḣ:Ω₀ + + + +n1->n4 + + +∂ₜ + + + +n7 + +dynamics_•1:Ω₁ + + + +n1->n7 + + +d₀ + + + +n12 + +dynamics_•6:Ω₁ + + + +n1->n12 + + +d₀ + + + +n35 + +Ω₀×ΩC + + + +n1->n35 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n36 + +Ω₁×Ω₁ + + + +n2->n36 + + +π₁ + + + +n3 + +n:ΩC + + + +n33 + +ΩC×ΩL + + + +n3->n33 + + +π₁ + + + +n41 + +Ω•×ΩC + + + +n3->n41 + + +π₂ + + + +n44 + +Σ1 + + + +n3->n44 + + + + + +n45 + +Σ2 + + + +n3->n45 + + + + + +n32 + + + +n4->n32 + + + + + +n5 + +dynamics_mult_1:Ω• + + + +n38 + +Ω•×Ω• + + + +n5->n38 + + +π₁ + + + +n6 + +dynamics_mult_2:Ω₁ + + + +n29 + +•_8_1:Ω̃₁ + + + +n6->n29 + + +⋆₁ + + + +n7->n36 + + +π₂ + + + +n8 + +dynamics_•2:Ω• + + + +n37 + +Ω₁×Ω• + + + +n8->n37 + + +π₂ + + + +n9 + +dynamics_•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +dynamics_•4:Ω• + + + +n34 + +Ω•×Ω• + + + +n10->n34 + + +π₁ + + + +n11 + +dynamics_•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +dynamics_•7:Ω• + + + +n13->n34 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n33 + + +π₂ + + + +n15 + +dynamics_•8:Ω• + + + +n15->n38 + + +π₂ + + + +n16 + +dynamics_•9:Ω₀ + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n44 + + + + + +n18 + +dynamics_sum_1:ΩC + + + +n18->n35 + + +π₂ + + + +n19 + +dynamics_mult_3:Ω₁ + + + +n19->n37 + + +π₁ + + + +n20 + +stress_A:ΩC + + + +n42 + +Ω•×ΩC + + + +n20->n42 + + +π₂ + + + +n21 + +stress_ρ:ΩC + + + +n40 + +ΩC×ΩC + + + +n21->n40 + + +π₁ + + + +n22 + +stress_g:ΩC + + + +n22->n40 + + +π₂ + + + +n23 + +stress_•1:Ω• + + + +n23->n42 + + +π₁ + + + +n24 + +2:ΩL + + + +n39 + +ΩL×ΩC + + + +n24->n39 + + +π₁ + + + +n24->n45 + + + + + +n25 + +stress_sum_1:ΩC + + + +n25->n39 + + +π₂ + + + +n26 + +stress_•2:Ω• + + + +n43 + +Ω•×Ω• + + + +n26->n43 + + +π₂ + + + +n27 + +stress_•3:Ω• + + + +n27->n41 + + +π₁ + + + +n28 + +stress_mult_1:Ω• + + + +n28->n43 + + +π₁ + + + +n30 + +•_8_2:Ω̃₂ + + + +n29->n30 + + +dual_d₁ + + + +n30->n4 + + +⋆₀⁻¹ + + + +n31 + + + +n31->n1 + + + + + +n31->n3 + + + + + +n31->n20 + + + + + +n31->n21 + + + + + +n31->n22 + + + + + +n33->n13 + + +- + + + +n34->n9 + + +^ + + + +n35->n16 + + +^ + + + +n36->n19 + + +* + + + +n37->n5 + + +* + + + +n38->n6 + + +* + + + +n39->n23 + + +/ + + + +n40->n27 + + +* + + + +n41->n26 + + +^ + + + +n42->n28 + + +* + + + +n43->n2 + + +* + + + +n44->n18 + + ++ + + + +n45->n25 + + ++ + + + diff --git a/dev/ice_dynamics/3a1df0a7.png b/dev/ice_dynamics/3a1df0a7.png deleted file mode 100644 index fc1702f6..00000000 Binary files a/dev/ice_dynamics/3a1df0a7.png and /dev/null differ diff --git a/dev/ice_dynamics/41c5266c.svg b/dev/ice_dynamics/41c5266c.svg new file mode 100644 index 00000000..13fb40cc --- /dev/null +++ b/dev/ice_dynamics/41c5266c.svg @@ -0,0 +1,402 @@ + + + + + + +G + + + +n1 + +h:Ω₀ + + + +n4 + +ḣ:Ω• + + + +n1->n4 + + +∂ₜ + + + +n7 + +•1:Ω• + + + +n1->n7 + + +d + + + +n12 + +•6:Ω• + + + +n1->n12 + + +d + + + +n24 + +Ω₀×Ω• + + + +n1->n24 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n25 + +Ω₁×Ω• + + + +n2->n25 + + +π₁ + + + +n3 + +n:ΩC + + + +n22 + +ΩC×ΩL + + + +n3->n22 + + +π₁ + + + +n28 + +Σ1 + + + +n3->n28 + + + + + +n21 + + + +n4->n21 + + + + + +n5 + +mult_1:Ω• + + + +n27 + +Ω•×Ω• + + + +n5->n27 + + +π₁ + + + +n6 + +mult_2:Ω• + + + +n6->n4 + + +⋆⋅d⋅⋆ + + + +n7->n25 + + +π₂ + + + +n8 + +•2:Ω• + + + +n26 + +Ω•×Ω• + + + +n8->n26 + + +π₂ + + + +n9 + +•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +•4:Ω• + + + +n23 + +Ω•×Ω• + + + +n10->n23 + + +π₁ + + + +n11 + +•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +•7:Ω• + + + +n13->n23 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n22 + + +π₂ + + + +n15 + +•8:Ω• + + + +n15->n27 + + +π₂ + + + +n16 + +•9:Ω• + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n28 + + + + + +n18 + +sum_1:Ω• + + + +n18->n24 + + +π₂ + + + +n19 + +mult_3:Ω• + + + +n19->n26 + + +π₁ + + + +n20 + + + +n20->n1 + + + + + +n20->n2 + + + + + +n20->n3 + + + + + +n22->n13 + + +- + + + +n23->n9 + + +^ + + + +n24->n16 + + +^ + + + +n25->n19 + + +* + + + +n26->n5 + + +* + + + +n27->n6 + + +* + + + +n28->n18 + + ++ + + + diff --git a/dev/ice_dynamics/821c4949.png b/dev/ice_dynamics/821c4949.png new file mode 100644 index 00000000..0e8ca6ce Binary files /dev/null and b/dev/ice_dynamics/821c4949.png differ diff --git a/dev/ice_dynamics/992fcaee.svg b/dev/ice_dynamics/992fcaee.svg new file mode 100644 index 00000000..8e9e18ff --- /dev/null +++ b/dev/ice_dynamics/992fcaee.svg @@ -0,0 +1,654 @@ + + + + + + +G + + + +n1 + +dynamics_h:Ω₀ + + + +n4 + +dynamics_ḣ:Ω₀ + + + +n1->n4 + + +∂ₜ + + + +n7 + +dynamics_•1:Ω₁ + + + +n1->n7 + + +d₀ + + + +n12 + +dynamics_•6:Ω₁ + + + +n1->n12 + + +d₀ + + + +n35 + +Ω₀×ΩC + + + +n1->n35 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n36 + +Ω₁×Ω₁ + + + +n2->n36 + + +π₁ + + + +n3 + +n:ΩC + + + +n33 + +ΩC×ΩL + + + +n3->n33 + + +π₁ + + + +n41 + +Ω•×ΩC + + + +n3->n41 + + +π₂ + + + +n44 + +Σ1 + + + +n3->n44 + + + + + +n45 + +Σ2 + + + +n3->n45 + + + + + +n32 + + + +n4->n32 + + + + + +n5 + +dynamics_mult_1:Ω• + + + +n38 + +Ω•×Ω• + + + +n5->n38 + + +π₁ + + + +n6 + +dynamics_mult_2:Ω₁ + + + +n29 + +•_8_1:Ω̃₀ + + + +n6->n29 + + +⋆₁ + + + +n7->n36 + + +π₂ + + + +n8 + +dynamics_•2:Ω• + + + +n37 + +Ω₁×Ω• + + + +n8->n37 + + +π₂ + + + +n9 + +dynamics_•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +dynamics_•4:Ω• + + + +n34 + +Ω•×Ω• + + + +n10->n34 + + +π₁ + + + +n11 + +dynamics_•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +dynamics_•7:Ω• + + + +n13->n34 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n33 + + +π₂ + + + +n15 + +dynamics_•8:Ω• + + + +n15->n38 + + +π₂ + + + +n16 + +dynamics_•9:Ω₀ + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n44 + + + + + +n18 + +dynamics_sum_1:ΩC + + + +n18->n35 + + +π₂ + + + +n19 + +dynamics_mult_3:Ω₁ + + + +n19->n37 + + +π₁ + + + +n20 + +stress_A:ΩC + + + +n42 + +Ω•×ΩC + + + +n20->n42 + + +π₂ + + + +n21 + +stress_ρ:ΩC + + + +n40 + +ΩC×ΩC + + + +n21->n40 + + +π₁ + + + +n22 + +stress_g:ΩC + + + +n22->n40 + + +π₂ + + + +n23 + +stress_•1:Ω• + + + +n23->n42 + + +π₁ + + + +n24 + +2:ΩL + + + +n39 + +ΩL×ΩC + + + +n24->n39 + + +π₁ + + + +n24->n45 + + + + + +n25 + +stress_sum_1:ΩC + + + +n25->n39 + + +π₂ + + + +n26 + +stress_•2:Ω• + + + +n43 + +Ω•×Ω• + + + +n26->n43 + + +π₂ + + + +n27 + +stress_•3:Ω• + + + +n27->n41 + + +π₁ + + + +n28 + +stress_mult_1:Ω• + + + +n28->n43 + + +π₁ + + + +n30 + +•_8_2:Ω̃₁ + + + +n29->n30 + + +dual_d₀ + + + +n30->n4 + + +⋆₀⁻¹ + + + +n31 + + + +n31->n1 + + + + + +n31->n3 + + + + + +n31->n20 + + + + + +n31->n21 + + + + + +n31->n22 + + + + + +n33->n13 + + +- + + + +n34->n9 + + +^ + + + +n35->n16 + + +^ + + + +n36->n19 + + +* + + + +n37->n5 + + +* + + + +n38->n6 + + +* + + + +n39->n23 + + +/ + + + +n40->n27 + + +* + + + +n41->n26 + + +^ + + + +n42->n28 + + +* + + + +n43->n2 + + +* + + + +n44->n18 + + ++ + + + +n45->n25 + + ++ + + + diff --git a/dev/ice_dynamics/9b0d6c48.svg b/dev/ice_dynamics/9b0d6c48.svg new file mode 100644 index 00000000..033688ad --- /dev/null +++ b/dev/ice_dynamics/9b0d6c48.svg @@ -0,0 +1,628 @@ + + + + + + +G + + + +n1 + +dynamics_h:Ω₀ + + + +n4 + +dynamics_ḣ:Ω• + + + +n1->n4 + + +∂ₜ + + + +n7 + +dynamics_•1:Ω• + + + +n1->n7 + + +d + + + +n12 + +dynamics_•6:Ω• + + + +n1->n12 + + +d + + + +n33 + +Ω₀×Ω• + + + +n1->n33 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n34 + +Ω₁×Ω• + + + +n2->n34 + + +π₁ + + + +n3 + +n:ΩC + + + +n31 + +ΩC×ΩL + + + +n3->n31 + + +π₁ + + + +n39 + +Ω•×ΩC + + + +n3->n39 + + +π₂ + + + +n42 + +Σ1 + + + +n3->n42 + + + + + +n43 + +Σ2 + + + +n3->n43 + + + + + +n30 + + + +n4->n30 + + + + + +n5 + +dynamics_mult_1:Ω• + + + +n36 + +Ω•×Ω• + + + +n5->n36 + + +π₁ + + + +n6 + +dynamics_mult_2:Ω• + + + +n6->n4 + + +⋆⋅d⋅⋆ + + + +n7->n34 + + +π₂ + + + +n8 + +dynamics_•2:Ω• + + + +n35 + +Ω•×Ω• + + + +n8->n35 + + +π₂ + + + +n9 + +dynamics_•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +dynamics_•4:Ω• + + + +n32 + +Ω•×Ω• + + + +n10->n32 + + +π₁ + + + +n11 + +dynamics_•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +dynamics_•7:Ω• + + + +n13->n32 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n31 + + +π₂ + + + +n15 + +dynamics_•8:Ω• + + + +n15->n36 + + +π₂ + + + +n16 + +dynamics_•9:Ω• + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n42 + + + + + +n18 + +dynamics_sum_1:Ω• + + + +n18->n33 + + +π₂ + + + +n19 + +dynamics_mult_3:Ω• + + + +n19->n35 + + +π₁ + + + +n20 + +stress_A:ΩC + + + +n40 + +Ω•×ΩC + + + +n20->n40 + + +π₂ + + + +n21 + +stress_ρ:ΩC + + + +n38 + +ΩC×ΩC + + + +n21->n38 + + +π₁ + + + +n22 + +stress_g:ΩC + + + +n22->n38 + + +π₂ + + + +n23 + +stress_•1:Ω• + + + +n23->n40 + + +π₁ + + + +n24 + +2:ΩL + + + +n37 + +ΩL×Ω• + + + +n24->n37 + + +π₁ + + + +n24->n43 + + + + + +n25 + +stress_sum_1:Ω• + + + +n25->n37 + + +π₂ + + + +n26 + +stress_•2:Ω• + + + +n41 + +Ω•×Ω• + + + +n26->n41 + + +π₂ + + + +n27 + +stress_•3:Ω• + + + +n27->n39 + + +π₁ + + + +n28 + +stress_mult_1:Ω• + + + +n28->n41 + + +π₁ + + + +n29 + + + +n29->n1 + + + + + +n29->n3 + + + + + +n29->n20 + + + + + +n29->n21 + + + + + +n29->n22 + + + + + +n31->n13 + + +- + + + +n32->n9 + + +^ + + + +n33->n16 + + +^ + + + +n34->n19 + + +* + + + +n35->n5 + + +* + + + +n36->n6 + + +* + + + +n37->n23 + + +/ + + + +n38->n27 + + +* + + + +n39->n26 + + +^ + + + +n40->n28 + + +* + + + +n41->n2 + + +* + + + +n42->n18 + + ++ + + + +n43->n25 + + ++ + + + diff --git a/dev/ice_dynamics/9cd40f49.svg b/dev/ice_dynamics/9cd40f49.svg deleted file mode 100644 index 1d566dde..00000000 --- a/dev/ice_dynamics/9cd40f49.svg +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - -G - - - -n1 - -h:Ω₀ - - - -n4 - -ḣ:Ω• - - - -n1->n4 - - -∂ₜ - - - -n7 - -•1:Ω• - - - -n1->n7 - - -d - - - -n12 - -•6:Ω• - - - -n1->n12 - - -d - - - -n24 - -Ω₀×Ω• - - - -n1->n24 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n25 - -Ω₁×Ω• - - - -n2->n25 - - -π₁ - - - -n3 - -n:ΩC - - - -n22 - -ΩC×ΩL - - - -n3->n22 - - -π₁ - - - -n28 - -Σ1 - - - -n3->n28 - - - - - -n21 - - - -n4->n21 - - - - - -n5 - -mult_1:Ω• - - - -n27 - -Ω•×Ω• - - - -n5->n27 - - -π₁ - - - -n6 - -mult_2:Ω• - - - -n6->n4 - - -⋆⋅d⋅⋆ - - - -n7->n25 - - -π₂ - - - -n8 - -•2:Ω• - - - -n26 - -Ω•×Ω• - - - -n8->n26 - - -π₂ - - - -n9 - -•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -•4:Ω• - - - -n23 - -Ω•×Ω• - - - -n10->n23 - - -π₁ - - - -n11 - -•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -•7:Ω• - - - -n13->n23 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n22 - - -π₂ - - - -n15 - -•8:Ω• - - - -n15->n27 - - -π₂ - - - -n16 - -•9:Ω• - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n28 - - - - - -n18 - -sum_1:Ω• - - - -n18->n24 - - -π₂ - - - -n19 - -mult_3:Ω• - - - -n19->n26 - - -π₁ - - - -n20 - - - -n20->n1 - - - - - -n20->n2 - - - - - -n20->n3 - - - - - -n22->n13 - - -- - - - -n23->n9 - - -^ - - - -n24->n16 - - -^ - - - -n25->n19 - - -* - - - -n26->n5 - - -* - - - -n27->n6 - - -* - - - -n28->n18 - - -+ - - - diff --git a/dev/ice_dynamics/a417ef58.png b/dev/ice_dynamics/a417ef58.png deleted file mode 100644 index 432883e8..00000000 Binary files a/dev/ice_dynamics/a417ef58.png and /dev/null differ diff --git a/dev/ice_dynamics/abc09bbd.png b/dev/ice_dynamics/abc09bbd.png new file mode 100644 index 00000000..36f09545 Binary files /dev/null and b/dev/ice_dynamics/abc09bbd.png differ diff --git a/dev/ice_dynamics/b2ad3fa0.png b/dev/ice_dynamics/b2ad3fa0.png new file mode 100644 index 00000000..bf9dd7c6 Binary files /dev/null and b/dev/ice_dynamics/b2ad3fa0.png differ diff --git a/dev/ice_dynamics/b38880b5.svg b/dev/ice_dynamics/b38880b5.svg new file mode 100644 index 00000000..47d6b27d --- /dev/null +++ b/dev/ice_dynamics/b38880b5.svg @@ -0,0 +1,672 @@ + + + + + + +G + + + +n1 + +dynamics_h:Ω₀ + + + +n4 + +dynamics_ḣ:Ω₀ + + + +n1->n4 + + +∂ₜ + + + +n7 + +dynamics_•1:Ω₁ + + + +n1->n7 + + +d₀ + + + +n12 + +dynamics_•6:Ω₁ + + + +n1->n12 + + +d₀ + + + +n35 + +Ω₀×ΩC + + + +n1->n35 + + +π₁ + + + +n2 + +Γ:Ω₁ + + + +n36 + +Ω₁×Ω₁ + + + +n2->n36 + + +π₁ + + + +n3 + +n:ΩC + + + +n33 + +ΩC×ΩL + + + +n3->n33 + + +π₁ + + + +n41 + +Ω•×ΩC + + + +n3->n41 + + +π₂ + + + +n44 + +Σ1 + + + +n3->n44 + + + + + +n45 + +Σ2 + + + +n3->n45 + + + + + +n32 + + + +n4->n32 + + + + + +n5 + +dynamics_mult_1:Ω• + + + +n38 + +Ω•×Ω• + + + +n5->n38 + + +π₁ + + + +n6 + +dynamics_mult_2:Ω₁ + + + +n29 + +•_8_1:Ω̃₁ + + + +n6->n29 + + +⋆₁ + + + +n7->n36 + + +π₂ + + + +n8 + +dynamics_•2:Ω• + + + +n37 + +Ω₁×Ω• + + + +n8->n37 + + +π₂ + + + +n9 + +dynamics_•3:Ω• + + + +n9->n8 + + +avg₀₁ + + + +n10 + +dynamics_•4:Ω• + + + +n34 + +Ω•×Ω• + + + +n10->n34 + + +π₁ + + + +n11 + +dynamics_•5:Ω• + + + +n11->n10 + + +mag + + + +n12->n11 + + + + + + +n13 + +dynamics_•7:Ω• + + + +n13->n34 + + +π₂ + + + +n14 + +1:ΩL + + + +n14->n33 + + +π₂ + + + +n15 + +dynamics_•8:Ω• + + + +n15->n38 + + +π₂ + + + +n16 + +dynamics_•9:Ω₀ + + + +n16->n15 + + +avg₀₁ + + + +n17 + +2:ΩL + + + +n17->n44 + + + + + +n18 + +dynamics_sum_1:ΩC + + + +n18->n35 + + +π₂ + + + +n19 + +dynamics_mult_3:Ω₁ + + + +n19->n37 + + +π₁ + + + +n20 + +stress_A:ΩC + + + +n42 + +Ω•×ΩC + + + +n20->n42 + + +π₂ + + + +n21 + +stress_ρ:ΩC + + + +n40 + +ΩC×ΩC + + + +n21->n40 + + +π₁ + + + +n22 + +stress_g:ΩC + + + +n22->n40 + + +π₂ + + + +n23 + +stress_•1:Ω• + + + +n23->n42 + + +π₁ + + + +n24 + +2:ΩL + + + +n39 + +ΩL×ΩC + + + +n24->n39 + + +π₁ + + + +n24->n45 + + + + + +n25 + +stress_sum_1:ΩC + + + +n25->n39 + + +π₂ + + + +n26 + +stress_•2:Ω• + + + +n43 + +Ω•×Ω• + + + +n26->n43 + + +π₂ + + + +n27 + +stress_•3:Ω• + + + +n27->n41 + + +π₁ + + + +n28 + +stress_mult_1:Ω• + + + +n28->n43 + + +π₁ + + + +n30 + +•_8_2:Ω̃₂ + + + +n29->n30 + + +dual_d₁ + + + +n30->n4 + + +⋆₀⁻¹ + + + +n31 + + + +n31->n1 + + + + + +n31->n3 + + + + + +n31->n14 + + + + + +n31->n17 + + + + + +n31->n20 + + + + + +n31->n21 + + + + + +n31->n22 + + + + + +n31->n24 + + + + + +n33->n13 + + +- + + + +n34->n9 + + +^ + + + +n35->n16 + + +^ + + + +n36->n19 + + +* + + + +n37->n5 + + +* + + + +n38->n6 + + +* + + + +n39->n23 + + +/ + + + +n40->n27 + + +* + + + +n41->n26 + + +^ + + + +n42->n28 + + +* + + + +n43->n2 + + +* + + + +n44->n18 + + ++ + + + +n45->n25 + + ++ + + + diff --git a/dev/ice_dynamics/b5968a26.png b/dev/ice_dynamics/b5968a26.png new file mode 100644 index 00000000..b0b11812 Binary files /dev/null and b/dev/ice_dynamics/b5968a26.png differ diff --git a/dev/ice_dynamics/c36a34a6.png b/dev/ice_dynamics/c36a34a6.png deleted file mode 100644 index f8d2913a..00000000 Binary files a/dev/ice_dynamics/c36a34a6.png and /dev/null differ diff --git a/dev/ice_dynamics/c3cebee9.svg b/dev/ice_dynamics/c3cebee9.svg deleted file mode 100644 index 98bd52f3..00000000 --- a/dev/ice_dynamics/c3cebee9.svg +++ /dev/null @@ -1,672 +0,0 @@ - - - - - - -G - - - -n1 - -dynamics_h:Ω₀ - - - -n4 - -dynamics_ḣ:Ω₀ - - - -n1->n4 - - -∂ₜ - - - -n7 - -dynamics_•1:Ω₁ - - - -n1->n7 - - -d₀ - - - -n12 - -dynamics_•6:Ω₁ - - - -n1->n12 - - -d₀ - - - -n35 - -Ω₀×ΩC - - - -n1->n35 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n36 - -Ω₁×Ω₁ - - - -n2->n36 - - -π₁ - - - -n3 - -n:ΩC - - - -n33 - -ΩC×ΩL - - - -n3->n33 - - -π₁ - - - -n41 - -Ω•×ΩC - - - -n3->n41 - - -π₂ - - - -n44 - -Σ1 - - - -n3->n44 - - - - - -n45 - -Σ2 - - - -n3->n45 - - - - - -n32 - - - -n4->n32 - - - - - -n5 - -dynamics_mult_1:Ω• - - - -n38 - -Ω•×Ω• - - - -n5->n38 - - -π₁ - - - -n6 - -dynamics_mult_2:Ω₁ - - - -n29 - -•_8_1:Ω̃₁ - - - -n6->n29 - - -⋆₁ - - - -n7->n36 - - -π₂ - - - -n8 - -dynamics_•2:Ω• - - - -n37 - -Ω₁×Ω• - - - -n8->n37 - - -π₂ - - - -n9 - -dynamics_•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -dynamics_•4:Ω• - - - -n34 - -Ω•×Ω• - - - -n10->n34 - - -π₁ - - - -n11 - -dynamics_•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -dynamics_•7:Ω• - - - -n13->n34 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n33 - - -π₂ - - - -n15 - -dynamics_•8:Ω• - - - -n15->n38 - - -π₂ - - - -n16 - -dynamics_•9:Ω₀ - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n44 - - - - - -n18 - -dynamics_sum_1:ΩC - - - -n18->n35 - - -π₂ - - - -n19 - -dynamics_mult_3:Ω₁ - - - -n19->n37 - - -π₁ - - - -n20 - -stress_A:ΩC - - - -n42 - -Ω•×ΩC - - - -n20->n42 - - -π₂ - - - -n21 - -stress_ρ:ΩC - - - -n40 - -ΩC×ΩC - - - -n21->n40 - - -π₁ - - - -n22 - -stress_g:ΩC - - - -n22->n40 - - -π₂ - - - -n23 - -stress_•1:Ω• - - - -n23->n42 - - -π₁ - - - -n24 - -2:ΩL - - - -n39 - -ΩL×ΩC - - - -n24->n39 - - -π₁ - - - -n24->n45 - - - - - -n25 - -stress_sum_1:ΩC - - - -n25->n39 - - -π₂ - - - -n26 - -stress_•2:Ω• - - - -n43 - -Ω•×Ω• - - - -n26->n43 - - -π₂ - - - -n27 - -stress_•3:Ω• - - - -n27->n41 - - -π₁ - - - -n28 - -stress_mult_1:Ω• - - - -n28->n43 - - -π₁ - - - -n30 - -•_8_2:Ω̃₂ - - - -n29->n30 - - -dual_d₁ - - - -n30->n4 - - -⋆₀⁻¹ - - - -n31 - - - -n31->n1 - - - - - -n31->n3 - - - - - -n31->n14 - - - - - -n31->n17 - - - - - -n31->n20 - - - - - -n31->n21 - - - - - -n31->n22 - - - - - -n31->n24 - - - - - -n33->n13 - - -- - - - -n34->n9 - - -^ - - - -n35->n16 - - -^ - - - -n36->n19 - - -* - - - -n37->n5 - - -* - - - -n38->n6 - - -* - - - -n39->n23 - - -/ - - - -n40->n27 - - -* - - - -n41->n26 - - -^ - - - -n42->n28 - - -* - - - -n43->n2 - - -* - - - -n44->n18 - - -+ - - - -n45->n25 - - -+ - - - diff --git a/dev/ice_dynamics/d32d6898.png b/dev/ice_dynamics/d32d6898.png deleted file mode 100644 index c294b06a..00000000 Binary files a/dev/ice_dynamics/d32d6898.png and /dev/null differ diff --git a/dev/ice_dynamics/debe43e8.svg b/dev/ice_dynamics/debe43e8.svg deleted file mode 100644 index cd6d6566..00000000 --- a/dev/ice_dynamics/debe43e8.svg +++ /dev/null @@ -1,628 +0,0 @@ - - - - - - -G - - - -n1 - -dynamics_h:Ω₀ - - - -n4 - -dynamics_ḣ:Ω• - - - -n1->n4 - - -∂ₜ - - - -n7 - -dynamics_•1:Ω• - - - -n1->n7 - - -d - - - -n12 - -dynamics_•6:Ω• - - - -n1->n12 - - -d - - - -n33 - -Ω₀×Ω• - - - -n1->n33 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n34 - -Ω₁×Ω• - - - -n2->n34 - - -π₁ - - - -n3 - -n:ΩC - - - -n31 - -ΩC×ΩL - - - -n3->n31 - - -π₁ - - - -n39 - -Ω•×ΩC - - - -n3->n39 - - -π₂ - - - -n42 - -Σ1 - - - -n3->n42 - - - - - -n43 - -Σ2 - - - -n3->n43 - - - - - -n30 - - - -n4->n30 - - - - - -n5 - -dynamics_mult_1:Ω• - - - -n36 - -Ω•×Ω• - - - -n5->n36 - - -π₁ - - - -n6 - -dynamics_mult_2:Ω• - - - -n6->n4 - - -⋆⋅d⋅⋆ - - - -n7->n34 - - -π₂ - - - -n8 - -dynamics_•2:Ω• - - - -n35 - -Ω•×Ω• - - - -n8->n35 - - -π₂ - - - -n9 - -dynamics_•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -dynamics_•4:Ω• - - - -n32 - -Ω•×Ω• - - - -n10->n32 - - -π₁ - - - -n11 - -dynamics_•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -dynamics_•7:Ω• - - - -n13->n32 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n31 - - -π₂ - - - -n15 - -dynamics_•8:Ω• - - - -n15->n36 - - -π₂ - - - -n16 - -dynamics_•9:Ω• - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n42 - - - - - -n18 - -dynamics_sum_1:Ω• - - - -n18->n33 - - -π₂ - - - -n19 - -dynamics_mult_3:Ω• - - - -n19->n35 - - -π₁ - - - -n20 - -stress_A:ΩC - - - -n40 - -Ω•×ΩC - - - -n20->n40 - - -π₂ - - - -n21 - -stress_ρ:ΩC - - - -n38 - -ΩC×ΩC - - - -n21->n38 - - -π₁ - - - -n22 - -stress_g:ΩC - - - -n22->n38 - - -π₂ - - - -n23 - -stress_•1:Ω• - - - -n23->n40 - - -π₁ - - - -n24 - -2:ΩL - - - -n37 - -ΩL×Ω• - - - -n24->n37 - - -π₁ - - - -n24->n43 - - - - - -n25 - -stress_sum_1:Ω• - - - -n25->n37 - - -π₂ - - - -n26 - -stress_•2:Ω• - - - -n41 - -Ω•×Ω• - - - -n26->n41 - - -π₂ - - - -n27 - -stress_•3:Ω• - - - -n27->n39 - - -π₁ - - - -n28 - -stress_mult_1:Ω• - - - -n28->n41 - - -π₁ - - - -n29 - - - -n29->n1 - - - - - -n29->n3 - - - - - -n29->n20 - - - - - -n29->n21 - - - - - -n29->n22 - - - - - -n31->n13 - - -- - - - -n32->n9 - - -^ - - - -n33->n16 - - -^ - - - -n34->n19 - - -* - - - -n35->n5 - - -* - - - -n36->n6 - - -* - - - -n37->n23 - - -/ - - - -n38->n27 - - -* - - - -n39->n26 - - -^ - - - -n40->n28 - - -* - - - -n41->n2 - - -* - - - -n42->n18 - - -+ - - - -n43->n25 - - -+ - - - diff --git a/dev/ice_dynamics/e6cf5deb.svg b/dev/ice_dynamics/e6cf5deb.svg deleted file mode 100644 index 762099ac..00000000 --- a/dev/ice_dynamics/e6cf5deb.svg +++ /dev/null @@ -1,654 +0,0 @@ - - - - - - -G - - - -n1 - -dynamics_h:Ω₀ - - - -n4 - -dynamics_ḣ:Ω₀ - - - -n1->n4 - - -∂ₜ - - - -n7 - -dynamics_•1:Ω₁ - - - -n1->n7 - - -d₀ - - - -n12 - -dynamics_•6:Ω₁ - - - -n1->n12 - - -d₀ - - - -n35 - -Ω₀×ΩC - - - -n1->n35 - - -π₁ - - - -n2 - -Γ:Ω₁ - - - -n36 - -Ω₁×Ω₁ - - - -n2->n36 - - -π₁ - - - -n3 - -n:ΩC - - - -n33 - -ΩC×ΩL - - - -n3->n33 - - -π₁ - - - -n41 - -Ω•×ΩC - - - -n3->n41 - - -π₂ - - - -n44 - -Σ1 - - - -n3->n44 - - - - - -n45 - -Σ2 - - - -n3->n45 - - - - - -n32 - - - -n4->n32 - - - - - -n5 - -dynamics_mult_1:Ω• - - - -n38 - -Ω•×Ω• - - - -n5->n38 - - -π₁ - - - -n6 - -dynamics_mult_2:Ω₁ - - - -n29 - -•_8_1:Ω̃₁ - - - -n6->n29 - - -⋆₁ - - - -n7->n36 - - -π₂ - - - -n8 - -dynamics_•2:Ω• - - - -n37 - -Ω₁×Ω• - - - -n8->n37 - - -π₂ - - - -n9 - -dynamics_•3:Ω• - - - -n9->n8 - - -avg₀₁ - - - -n10 - -dynamics_•4:Ω• - - - -n34 - -Ω•×Ω• - - - -n10->n34 - - -π₁ - - - -n11 - -dynamics_•5:Ω• - - - -n11->n10 - - -mag - - - -n12->n11 - - - - - - -n13 - -dynamics_•7:Ω• - - - -n13->n34 - - -π₂ - - - -n14 - -1:ΩL - - - -n14->n33 - - -π₂ - - - -n15 - -dynamics_•8:Ω• - - - -n15->n38 - - -π₂ - - - -n16 - -dynamics_•9:Ω₀ - - - -n16->n15 - - -avg₀₁ - - - -n17 - -2:ΩL - - - -n17->n44 - - - - - -n18 - -dynamics_sum_1:ΩC - - - -n18->n35 - - -π₂ - - - -n19 - -dynamics_mult_3:Ω₁ - - - -n19->n37 - - -π₁ - - - -n20 - -stress_A:ΩC - - - -n42 - -Ω•×ΩC - - - -n20->n42 - - -π₂ - - - -n21 - -stress_ρ:ΩC - - - -n40 - -ΩC×ΩC - - - -n21->n40 - - -π₁ - - - -n22 - -stress_g:ΩC - - - -n22->n40 - - -π₂ - - - -n23 - -stress_•1:Ω• - - - -n23->n42 - - -π₁ - - - -n24 - -2:ΩL - - - -n39 - -ΩL×ΩC - - - -n24->n39 - - -π₁ - - - -n24->n45 - - - - - -n25 - -stress_sum_1:ΩC - - - -n25->n39 - - -π₂ - - - -n26 - -stress_•2:Ω• - - - -n43 - -Ω•×Ω• - - - -n26->n43 - - -π₂ - - - -n27 - -stress_•3:Ω• - - - -n27->n41 - - -π₁ - - - -n28 - -stress_mult_1:Ω• - - - -n28->n43 - - -π₁ - - - -n30 - -•_8_2:Ω̃₂ - - - -n29->n30 - - -dual_d₁ - - - -n30->n4 - - -⋆₀⁻¹ - - - -n31 - - - -n31->n1 - - - - - -n31->n3 - - - - - -n31->n20 - - - - - -n31->n21 - - - - - -n31->n22 - - - - - -n33->n13 - - -- - - - -n34->n9 - - -^ - - - -n35->n16 - - -^ - - - -n36->n19 - - -* - - - -n37->n5 - - -* - - - -n38->n6 - - -* - - - -n39->n23 - - -/ - - - -n40->n27 - - -* - - - -n41->n26 - - -^ - - - -n42->n28 - - -* - - - -n43->n2 - - -* - - - -n44->n18 - - -+ - - - -n45->n25 - - -+ - - - diff --git a/dev/ice_dynamics/ec886b80.png b/dev/ice_dynamics/ec886b80.png deleted file mode 100644 index 5abba2d8..00000000 Binary files a/dev/ice_dynamics/ec886b80.png and /dev/null differ diff --git a/dev/ice_dynamics/f0b7cf8a.png b/dev/ice_dynamics/f0b7cf8a.png deleted file mode 100644 index 89a1aa9e..00000000 Binary files a/dev/ice_dynamics/f0b7cf8a.png and /dev/null differ diff --git a/dev/ice_dynamics/f7c9e917.png b/dev/ice_dynamics/f7c9e917.png deleted file mode 100644 index 10b8d56e..00000000 Binary files a/dev/ice_dynamics/f7c9e917.png and /dev/null differ diff --git a/dev/ice_dynamics/index.html b/dev/ice_dynamics/index.html index d5cf9990..eff8d5dd 100644 --- a/dev/ice_dynamics/index.html +++ b/dev/ice_dynamics/index.html @@ -27,7 +27,7 @@ ḣ == ∘(⋆, d, ⋆)(Γ * d(h) * avg₀₁(mag(♯(d(h)))^(n-1)) * avg₀₁(h^(n+2))) end -to_graphviz(halfar_eq2)Example block output

And here, a formulation of Glen's law from J.W. Glen's 1958 "The flow law of ice".

glens_law = @decapode begin
+to_graphviz(halfar_eq2)
Example block output

And here, a formulation of Glen's law from J.W. Glen's 1958 "The flow law of ice".

glens_law = @decapode begin
   #Γ::Form0
   Γ::Form1
   (A,ρ,g,n)::Constant
@@ -35,7 +35,7 @@
   Γ == (2/(n+2))*A*(ρ*g)^n
 end
 
-to_graphviz(glens_law)
Example block output

Composing models

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.

ice_dynamics_composition_diagram = @relation () begin
+to_graphviz(glens_law)
Example block output

Composing models

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.

ice_dynamics_composition_diagram = @relation () begin
   dynamics(Γ,n)
   stress(Γ,n)
 end
@@ -43,59 +43,59 @@
 to_graphviz(ice_dynamics_composition_diagram, box_labels=:name, junction_labels=:variable, prog="circo")
- - + G - + n1 - -phyto + +phyto n3 - -N + +N n1&%2345;&%2345;n3 - + n4 - -W + +W n1&%2345;&%2345;n4 - + n2 - -hydro + +hydro n2&%2345;&%2345;n3 - + n2&%2345;&%2345;n4 - + @@ -280,7 +280,7 @@ [Open(Phytodynamics, [:n, :w]), Open(Hydrodynamics, [:n, :w])]) Klausmeier = apex(klausmeier_cospan) -to_graphviz(Klausmeier)Example block output

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

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

We 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

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.

function generate(sd, my_symbol; hodge=DiagonalHodge())
+to_graphviz(Klausmeier)
Example block output

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

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

We 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

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.

function generate(sd, my_symbol; hodge=DiagonalHodge())
   lap_mat = hodge_star(1,sd) * d(0,sd) * inv_hodge_star(0,sd) * dual_derivative(0,sd)
   dd_mat = dual_derivative(0,sd)
   ih_mat = inv_hodge_star(0,sd)
@@ -312,7 +312,7 @@
 end
 s,sd = circle(7, 500)
 
-scatter(sd[:point])
Example block output

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

# Instantiate Simulation
+scatter(sd[:point])
Example block output

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

# Instantiate Simulation
 fₘ = sim(sd, generate, DiagonalHodge())
(::Main.var"#f#1"{Main.var"#6#10"{Main.var"#4#8"{CombinatorialSpaces.DiscreteExteriorCalculus.EmbeddedDeltaDualComplex1D{Bool, Float64, Point2{Float64}}, SparseArrays.SparseMatrixCSC{Float64, Int64}, LinearAlgebra.Diagonal{Float64, Vector{Float64}}}}, Main.var"#6#10"{Main.var"#5#9"}, Main.var"#6#10"{Main.var"#3#7"{SparseArrays.SparseMatrixCSC{Float64, Int64}}}}) (generic function with 1 method)

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

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

# Define Initial Conditions
 n_dist = Normal(pi)
 n = [pdf(n_dist, t)*(√(2pi))*7.2 + 0.08 - 5e-2 for t in range(0,2pi; length=ne(sd))]
@@ -333,26 +333,26 @@
 sol.retcode
ReturnCode.Success = 1

Animation

Let's perform some basic visualization and analysis of our results to verify our dynamics.

n = sol(0).N
 nₑ = sol(tₑ).N
 w = sol(0).W
128-element view(::Vector{Float64}, 129:256) with eltype Float64:
-  0.02378307447512045
-  0.8795933653376428
-  0.4362570023577117
-  0.930489338516365
-  0.3698694851480921
-  0.9645169638789393
-  0.31812820431091704
-  0.9837846683242236
-  0.27865296416165386
-  0.9907951156214047
+  0.02378307447971206
+  0.8795933653192783
+  0.43625700237445875
+  0.930489338501099
+  0.3698694851616143
+  0.9645169638667229
+  0.31812820432153494
+  0.9837846683146549
+  0.27865296416982804
+  0.9907951156140172
   ⋮
-  0.27303368741226103
- -0.04560167016982
-  0.22531638056409947
- -0.06252647972324771
-  0.16949227196915437
- -0.05757222734142635
-  0.06218579533710285
- -0.003151464590984988
-  0.004343141829423863
# Animate dynamics
+  0.2730336874141576
+ -0.045601670171014685
+  0.22531638056606218
+ -0.06252647972488579
+  0.16949227197114924
+ -0.05757222734320094
+  0.06218579533833296
+ -0.003151464591243574
+  0.004343141829212294
# Animate dynamics
 function save_dynamics(form_name, framerate, filename)
   time = Observable(0.0)
   ys = @lift(getproperty(sol($time), form_name))
@@ -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/overview/4690d045.svg b/dev/overview/4690d045.svg deleted file mode 100644 index ae42f981..00000000 --- a/dev/overview/4690d045.svg +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - -G - - - -n1 - -C:Ω₀ - - - -n3 - -diffusion_•1:Ω• - - - -n1->n3 - - -d₀ - - - -n6 - -superposition_Ċ:Ω₀ - - - -n1->n6 - - -∂ₜ - - - -n12 - -Ω₀×Ω₁ - - - -n1->n12 - - -π₁ - - - -n2 - -ϕ₁:Ω₁ - - - -n13 - -Σ1 - - - -n2->n13 - - - - - -n3->n2 - - -k - - - -n4 - -ϕ₂:Ω₁ - - - -n4->n13 - - - - - -n5 - -V:Ω₁ - - - -n5->n12 - - -π₂ - - - -n11 - - - -n6->n11 - - - - - -n7 - -ϕ:Ω₁ - - - -n9 - -superposition_•1:Ω• - - - -n7->n9 - - -⋆₁ - - - -n8 - -superposition_sum_1:Ω• - - - -n8->n6 - - -⋆₀⁻¹ - - - -n9->n8 - - -dual_d₁ - - - -n10 - - - -n10->n1 - - - - - -n10->n5 - - - - - -n12->n4 - - -∧₀₁ - - - -n13->n7 - - -+ - - - diff --git a/dev/overview/9d766568.png b/dev/overview/9d766568.png deleted file mode 100644 index fa31bc05..00000000 Binary files a/dev/overview/9d766568.png and /dev/null differ diff --git a/dev/overview/c49e3dcd.png b/dev/overview/c49e3dcd.png new file mode 100644 index 00000000..6078a7d6 Binary files /dev/null and b/dev/overview/c49e3dcd.png differ diff --git a/dev/overview/c9437a29.svg b/dev/overview/c9437a29.svg new file mode 100644 index 00000000..dc132516 --- /dev/null +++ b/dev/overview/c9437a29.svg @@ -0,0 +1,187 @@ + + + + + + +G + + + +n1 + +C:Ω₀ + + + +n3 + +diffusion_•1:Ω• + + + +n1->n3 + + +d₀ + + + +n6 + +superposition_Ċ:Ω₀ + + + +n1->n6 + + +∂ₜ + + + +n12 + +Ω₀×Ω₁ + + + +n1->n12 + + +π₁ + + + +n2 + +ϕ₁:Ω₁ + + + +n13 + +Σ1 + + + +n2->n13 + + + + + +n3->n2 + + +k + + + +n4 + +ϕ₂:Ω₁ + + + +n4->n13 + + + + + +n5 + +V:Ω₁ + + + +n5->n12 + + +π₂ + + + +n11 + + + +n6->n11 + + + + + +n7 + +ϕ:Ω₁ + + + +n9 + +superposition_•1:Ω• + + + +n7->n9 + + +⋆₁ + + + +n8 + +superposition_sum_1:Ω• + + + +n8->n6 + + +⋆₀⁻¹ + + + +n9->n8 + + +dual_d₁ + + + +n10 + + + +n10->n1 + + + + + +n10->n5 + + + + + +n12->n4 + + +∧₀₁ + + + +n13->n7 + + ++ + + + diff --git a/dev/overview/ce89d084.png b/dev/overview/ce89d084.png new file mode 100644 index 00000000..e8344568 Binary files /dev/null and b/dev/overview/ce89d084.png differ diff --git a/dev/overview/fe567c96.png b/dev/overview/fe567c96.png deleted file mode 100644 index c8a36768..00000000 Binary files a/dev/overview/fe567c96.png and /dev/null differ diff --git a/dev/overview/index.html b/dev/overview/index.html index 170f8082..c0134045 100644 --- a/dev/overview/index.html +++ b/dev/overview/index.html @@ -11,19 +11,19 @@ to_graphviz(Variable) - - - + + G - + n1 - -C:Ω₀ + +C:Ω₀ n2 - -dC:Ω₁ + +dC:Ω₁ @@ -76,14 +76,14 @@ n3&%2345;>n1 - - + + n3&%2345;>n2 - - + + @@ -101,32 +101,32 @@ to_graphviz(Equation) - - - + + G - + n1 - -C:Ω₀ + +C:Ω₀ n2 - -Ċ:Ω₀ + +Ċ:Ω₀ n1&%2345;>n2 - - -∂ₜ + + +∂ₜ n5 - -•2:Ω• + +•2:Ω• n1&%2345;>n5 - - -d₀ + + +d₀ @@ -205,54 +205,54 @@ n2&%2345;>n8 - - + + n3 - -ϕ:Ω₁ + +ϕ:Ω₁ n6 - -•3:Ω• + +•3:Ω• n3&%2345;>n6 - - -⋆₁ + + +⋆₁ n4 - -•1:Ω• + +•1:Ω• n4&%2345;>n2 - - -⋆₀⁻¹ + + +⋆₀⁻¹ n5&%2345;>n3 - - -k + + +k n6&%2345;>n4 - - -dual_d₁ + + +dual_d₁ @@ -261,8 +261,8 @@ n7&%2345;>n1 - - + + @@ -274,9 +274,10 @@ periodic_mesh = loadmesh(Torus_30x10()) point_map = loadmesh(Point_Map()) -fig, ax, ob = wireframe(plot_mesh) -ax.aspect = AxisAspect(3.0) -figExample block output

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.

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.

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.

using MLStyle
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+wireframe!(ax, plot_mesh)
+fig
Example block output

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.

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.

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.

using MLStyle
 
 function generate(sd, my_symbol; hodge=DiagonalHodge())
   op = @match my_symbol begin
@@ -291,9 +292,10 @@
 c_dist = MvNormal([7, 5], [1.5, 1.5])
 c = [pdf(c_dist, [p[1], p[2]]) for p in periodic_mesh[:point]]
 
-fig, ax, ob = mesh(plot_mesh; color=c[point_map])
-ax.aspect = AxisAspect(3.0)
-fig
Example block output

Finally, we solve this PDE problem using the Tsit5() solver and generate an animation of the result!

using LinearAlgebra
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+mesh!(ax, plot_mesh; color=c[point_map])
+fig
Example block output

Finally, we solve this PDE problem using the Tsit5() solver and generate an animation of the result!

using LinearAlgebra
 using ComponentArrays
 using OrdinaryDiffEq
 
@@ -307,14 +309,15 @@
 colors = [sol(t).C[point_map] for t in times]
 
 # Initial frame
-fig, ax, ob = mesh(plot_mesh, color=colors[1], colorrange = extrema(vcat(colors...)))
-ax.aspect = AxisAspect(3.0)
-Colorbar(fig[1,2], ob)
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+pmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))
+Colorbar(fig[1,2], pmsh)
 framerate = 30
 
 # Animation
 record(fig, "diffusion.gif", range(0.0, 100.0; length=150); framerate = 30) do t
-ob.color = sol(t).C[point_map]
+  pmsh.color = sol(t).C[point_map]
 end
"diffusion.gif"

Merging Multiple Physics

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.

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.

Diffusion = @decapode begin
   C::Form0
   ϕ::Form1
@@ -340,45 +343,45 @@
 end
true
to_graphviz(Diffusion)
to_graphviz(Advection)
to_graphviz(Superposition)
- - + G - + n1 - -diffusion + +diffusion n6 - -C + +C n1&%2345;&%2345;n6 - + n8 - -ϕ₁ + +ϕ₁ n1&%2345;&%2345;n8 - + n2 - -advection + +advection n2&%2345;&%2345;n6 - + n7 - -V + +V n2&%2345;&%2345;n7 - + n9 - -ϕ₂ + +ϕ₂ n2&%2345;&%2345;n9 - + n3 - -superposition + +superposition n3&%2345;&%2345;n6 - + n3&%2345;&%2345;n8 - + n3&%2345;&%2345;n9 - + n10 - -ϕ + +ϕ n3&%2345;&%2345;n10 - + n4&%2345;&%2345;n6 - + n5&%2345;&%2345;n7 - + @@ -749,7 +752,7 @@ Open(Superposition, [:ϕ₁, :ϕ₂, :ϕ, :C])]) DiffusionAdvection = apex(DiffusionAdvection_cospan) -to_graphviz(DiffusionAdvection)Example block output

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.

We could instead represent the domain as the surface of an object with equivalent boundaries in 3D.

using LinearAlgebra
+to_graphviz(DiffusionAdvection)
Example block output

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.

We could instead represent the domain as the surface of an object with equivalent boundaries in 3D.

using LinearAlgebra
 using MLStyle
 
 function generate(sd, my_symbol; hodge=DiagonalHodge())
@@ -779,12 +782,13 @@
 colors = [sol(t).C[point_map] for t in times]
 
 # Initial frame
-fig, ax, ob = mesh(plot_mesh, color=colors[1], colorrange = extrema(vcat(colors...)))
-ax.aspect = AxisAspect(3.0)
-Colorbar(fig[1,2], ob)
+fig = Figure()
+ax = CairoMakie.Axis(fig[1,1], aspect = AxisAspect(3.0))
+pmsh = mesh!(ax, plot_mesh; color=colors[1], colorrange = extrema(vcat(colors...)))
+Colorbar(fig[1,2], pmsh)
 framerate = 30
 
 # Animation
 record(fig, "diff_adv.gif", range(0.0, 100.0; length=150); framerate = 30) do t
-ob.color = sol(t).C[point_map]
-end
"diff_adv.gif"

```

+ pmsh.color = sol(t).C[point_map] +end
"diff_adv.gif"

```

diff --git a/dev/poiseuille/05b3a9bd.svg b/dev/poiseuille/05b3a9bd.svg new file mode 100644 index 00000000..8b05bc12 --- /dev/null +++ b/dev/poiseuille/05b3a9bd.svg @@ -0,0 +1,224 @@ + + + + + + +G + + + +n1 + +P:Ω₀ + + + +n7 + +∇P:Ω₁ + + + +n1->n7 + + +d₀ + + + +n2 + +q:Ω₁ + + + +n5 + +Δq:Ω₁ + + + +n2->n5 + + +Δ₁ + + + +n10 + +q̇:Ω₁ + + + +n2->n10 + + +∂ₜ + + + +n14 + +ΩC×Ω₁ + + + +n2->n14 + + +π₂ + + + +n3 + +R:ΩC + + + +n3->n14 + + +π₁ + + + +n4 + +μ̃:ΩC + + + +n13 + +ΩC×Ω₁ + + + +n4->n13 + + +π₁ + + + +n8 + +•2:Ω₁ + + + +n5->n8 + + +∂q + + + +n6 + +•1:Ω₁ + + + +n15 + +Σ1 + + + +n6->n15 + + + + + +n7->n15 + + + + + +n8->n13 + + +π₂ + + + +n9 + +•3:Ω₁ + + + +n9->n15 + + + + + +n12 + + + +n10->n12 + + + + + +n11 + + + +n11->n1 + + + + + +n11->n2 + + + + + +n11->n3 + + + + + +n11->n4 + + + + + +n13->n6 + + +* + + + +n14->n9 + + +* + + + +n15->n10 + + ++ + + + diff --git a/dev/poiseuille/8e529378.svg b/dev/poiseuille/8e529378.svg deleted file mode 100644 index 914bbaf5..00000000 --- a/dev/poiseuille/8e529378.svg +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - -G - - - -n1 - -P:Ω₀ - - - -n7 - -∇P:Ω₁ - - - -n1->n7 - - -d₀ - - - -n2 - -q:Ω₁ - - - -n5 - -Δq:Ω₁ - - - -n2->n5 - - -Δ₁ - - - -n10 - -q̇:Ω₁ - - - -n2->n10 - - -∂ₜ - - - -n14 - -ΩC×Ω₁ - - - -n2->n14 - - -π₂ - - - -n3 - -R:ΩC - - - -n3->n14 - - -π₁ - - - -n4 - -μ̃:ΩC - - - -n13 - -ΩC×Ω₁ - - - -n4->n13 - - -π₁ - - - -n8 - -•2:Ω₁ - - - -n5->n8 - - -∂q - - - -n6 - -•1:Ω₁ - - - -n15 - -Σ1 - - - -n6->n15 - - - - - -n7->n15 - - - - - -n8->n13 - - -π₂ - - - -n9 - -•3:Ω₁ - - - -n9->n15 - - - - - -n12 - - - -n10->n12 - - - - - -n11 - - - -n11->n1 - - - - - -n11->n2 - - - - - -n11->n3 - - - - - -n11->n4 - - - - - -n13->n6 - - -* - - - -n14->n9 - - -* - - - -n15->n10 - - -+ - - - diff --git a/dev/poiseuille/9c5f9149.svg b/dev/poiseuille/9c5f9149.svg deleted file mode 100644 index 17886da3..00000000 --- a/dev/poiseuille/9c5f9149.svg +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - -G - - - -n1 - -q:Ω₁ - - - -n8 - -q̇:Ω₁ - - - -n1->n8 - - -∂ₜ - - - -n14 - -•4:Ω₁ - - - -n1->n14 - - -Δ₁ - - - -n25 - -ΩC×Ω₁ - - - -n1->n25 - - -π₂ - - - -n27 - -Ω₀×Ω₁ - - - -n1->n27 - - -π₂ - - - -n2 - -P:Ω₀ - - - -n9 - -∇P:Ω₁ - - - -n2->n9 - - -d₀ - - - -n3 - -ρ:Ω₀ - - - -n7 - -ρ̇:Ω₀ - - - -n3->n7 - - -∂ₜ - - - -n26 - -ΩC×Ω₀ - - - -n3->n26 - - -π₂ - - - -n3->n27 - - -π₁ - - - -n4 - -k:ΩC - - - -n4->n26 - - -π₁ - - - -n5 - -R:ΩC - - - -n5->n25 - - -π₁ - - - -n6 - -μ̃:ΩC - - - -n23 - -ΩC×Ω• - - - -n6->n23 - - -π₁ - - - -n22 - - - -n7->n22 - - - - - -n8->n22 - - - - - -n24 - -Ω•×Ω₁ - - - -n9->n24 - - -π₂ - - - -n10 - -ρ_up:Ω₀ - - - -n10->n7 - - -∂ρ - - - -n11 - -•1:Ω₁ - - - -n29 - -Σ1 - - - -n11->n29 - - - - - -n12 - -•2:Ω• - - - -n12->n24 - - -π₁ - - - -n13 - -•3:Ω• - - - -n13->n23 - - -π₂ - - - -n14->n13 - - -∂q - - - -n15 - -•5:Ω₁ - - - -n15->n29 - - - - - -n16 - -sum_1:Ω₁ - - - -n28 - -ΩL×Ω₁ - - - -n16->n28 - - -π₂ - - - -n17 - -•6:Ω₁ - - - -n19 - -•_6_1:Ω̃₀ - - - -n17->n19 - - -⋆₁ - - - -n18 - --1:ΩL - - - -n18->n28 - - -π₁ - - - -n20 - -•_6_2:Ω̃₁ - - - -n19->n20 - - -dual_d₀ - - - -n20->n10 - - -⋆₀⁻¹ - - - -n21 - - - -n21->n1 - - - - - -n21->n3 - - - - - -n21->n4 - - - - - -n21->n5 - - - - - -n21->n6 - - - - - -n23->n12 - - -* - - - -n24->n11 - - -- - - - -n25->n15 - - -* - - - -n26->n2 - - -* - - - -n27->n16 - - -∧₀₁ - - - -n28->n17 - - -* - - - -n29->n8 - - -+ - - - diff --git a/dev/poiseuille/ef9626aa.svg b/dev/poiseuille/ef9626aa.svg new file mode 100644 index 00000000..cca18c6a --- /dev/null +++ b/dev/poiseuille/ef9626aa.svg @@ -0,0 +1,433 @@ + + + + + + +G + + + +n1 + +q:Ω₁ + + + +n8 + +q̇:Ω₁ + + + +n1->n8 + + +∂ₜ + + + +n14 + +•4:Ω₁ + + + +n1->n14 + + +Δ₁ + + + +n25 + +ΩC×Ω₁ + + + +n1->n25 + + +π₂ + + + +n27 + +Ω₀×Ω₁ + + + +n1->n27 + + +π₂ + + + +n2 + +P:Ω₀ + + + +n9 + +∇P:Ω₁ + + + +n2->n9 + + +d₀ + + + +n3 + +ρ:Ω₀ + + + +n7 + +ρ̇:Ω₀ + + + +n3->n7 + + +∂ₜ + + + +n26 + +ΩC×Ω₀ + + + +n3->n26 + + +π₂ + + + +n3->n27 + + +π₁ + + + +n4 + +k:ΩC + + + +n4->n26 + + +π₁ + + + +n5 + +R:ΩC + + + +n5->n25 + + +π₁ + + + +n6 + +μ̃:ΩC + + + +n23 + +ΩC×Ω• + + + +n6->n23 + + +π₁ + + + +n22 + + + +n7->n22 + + + + + +n8->n22 + + + + + +n24 + +Ω•×Ω₁ + + + +n9->n24 + + +π₂ + + + +n10 + +ρ_up:Ω₀ + + + +n10->n7 + + +∂ρ + + + +n11 + +•1:Ω₁ + + + +n29 + +Σ1 + + + +n11->n29 + + + + + +n12 + +•2:Ω• + + + +n12->n24 + + +π₁ + + + +n13 + +•3:Ω• + + + +n13->n23 + + +π₂ + + + +n14->n13 + + +∂q + + + +n15 + +•5:Ω₁ + + + +n15->n29 + + + + + +n16 + +sum_1:Ω₁ + + + +n28 + +ΩL×Ω₁ + + + +n16->n28 + + +π₂ + + + +n17 + +•6:Ω₁ + + + +n19 + +•_6_1:Ω̃₀ + + + +n17->n19 + + +⋆₁ + + + +n18 + +-1:ΩL + + + +n18->n28 + + +π₁ + + + +n20 + +•_6_2:Ω̃₁ + + + +n19->n20 + + +dual_d₀ + + + +n20->n10 + + +⋆₀⁻¹ + + + +n21 + + + +n21->n1 + + + + + +n21->n3 + + + + + +n21->n4 + + + + + +n21->n5 + + + + + +n21->n6 + + + + + +n23->n12 + + +* + + + +n24->n11 + + +- + + + +n25->n15 + + +* + + + +n26->n2 + + +* + + + +n27->n16 + + +∧₀₁ + + + +n28->n17 + + +* + + + +n29->n8 + + ++ + + + diff --git a/dev/poiseuille/index.html b/dev/poiseuille/index.html index db0554b8..876935d5 100644 --- a/dev/poiseuille/index.html +++ b/dev/poiseuille/index.html @@ -34,7 +34,7 @@ Poise = expand_operators(Poise) infer_types!(Poise, op1_inf_rules_1D, op2_inf_rules_1D) resolve_overloads!(Poise, op1_res_rules_1D, op2_res_rules_1D) -to_graphviz(Poise)Example block output

Defining the Semantics

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.

using MLStyle
+to_graphviz(Poise)
Example block output

Defining the Semantics

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.

using MLStyle
 include("../../examples/boundary_helpers.jl")
 
 function generate(sd, my_symbol; hodge=GeometricHodge())
@@ -173,19 +173,19 @@
  ComponentVector{Float64}(q = [-206.15225697183885], P = [10.0, 5.0])
  ComponentVector{Float64}(q = [-520.9665006970124], P = [10.0, 5.0])
  ComponentVector{Float64}(q = [-1061.8122613352489], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-2050.701221686718], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-3918.2885837449535], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-7662.6981903888], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-2050.701323620807], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-3918.2889484074135], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-7662.699250878012], P = [10.0, 5.0])
  ⋮
- ComponentVector{Float64}(q = [-2.5163517616858068e20], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-9.675030018368683e20], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-3.723068989076636e21], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-1.4337196904916158e22], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-5.524540694656894e22], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-2.129893224540613e23], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-8.215156447039265e23], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-3.169871349682816e24], P = [10.0, 5.0])
- ComponentVector{Float64}(q = [-5.115638375030341e24], P = [10.0, 5.0])

A Linear Pipe with Multiple Segments

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:

function linear_pipe(n::Int)
+ ComponentVector{Float64}(q = [-2.5163514257587657e20], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-9.675028778295925e20], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-3.723068199773999e21], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-1.433719394170305e22], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-5.524539089298827e22], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-2.129892426840319e23], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-8.21515268044665e23], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-3.1698699131680074e24], P = [10.0, 5.0])
+ ComponentVector{Float64}(q = [-5.115638380031789e24], P = [10.0, 5.0])

A Linear Pipe with Multiple Segments

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:

function linear_pipe(n::Int)
   s = EmbeddedDeltaSet1D{Bool,Point3D}()
   add_vertices!(s, n, point=[Point3D(i, 0, 0) for i in 1:n])
   add_edges!(s, 1:n-1, 2:n, edge_orientation=true)
@@ -213,15 +213,15 @@
  ComponentVector{Float64}(q = [2.471600821051096, 1.669349648605741, 1.1765327728146764, 1.0513571149790664, 1.2862830732329438, 1.6542988082063277, 1.877334455872725, 1.4133688122914647, 0.4456685217798253], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
  ComponentVector{Float64}(q = [1.8109484887135994, 1.0978250350779786, 0.6295262764141557, 0.4957834248919611, 0.6619437663946418, 0.9332951566603191, 1.0181587120476996, 0.5982478987276951, -0.22175973191497886], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
  ⋮
- ComponentVector{Float64}(q = [-9.480731562951805e23, -9.494974860754267e23, -9.501182060852792e23, -9.52223803333118e23, -9.523893149744158e23, -9.54654760144132e23, -9.549801197073098e23, -9.567903565084715e23, -9.577969835392403e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-9.567887788286766e23, -9.582262025010647e23, -9.588526286398619e23, -9.60977582823063e23, -9.611446157616158e23, -9.634308873841648e23, -9.637592377620625e23, -9.655861161843678e23, -9.6660199707308e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-9.655845236003787e23, -9.670351614834348e23, -9.676673463579633e23, -9.698118351982925e23, -9.699804036852707e23, -9.722876929511166e23, -9.726190618636094e23, -9.744627347419033e23, -9.754879546116716e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-9.744611283475211e23, -9.759251020461442e23, -9.765630982391839e23, -9.7872730185567e23, -9.788974193759622e23, -9.812259201334862e23, -9.815603347948158e23, -9.834209568795919e23, -9.844556014587847e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-9.834193352378417e23, -9.848967672942357e23, -9.85540628380483e23, -9.877247277831584e23, -9.87896408844505e23, -9.902463158222312e23, -9.905838044586298e23, -9.924615314114547e23, -9.935056873941343e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-9.924598938306274e23, -9.939509078202128e23, -9.94600688129604e23, -9.968048655973775e23, -9.969781253052977e23, -9.993496345559196e23, -9.99690226046687e23, -1.0015852146958389e24, -1.0026389696647948e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-1.0015835617731853e24, -1.0030882824978622e24, -1.0037440366051097e24, -1.0059684764756137e24, -1.0061433296243173e24, -1.0085366394545214e24, -1.0088803625629243e24, -1.0107927714345842e24, -1.0118562136888149e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-1.0107911043069454e24, -1.0123096579029141e24, -1.0129714403579179e24, -1.0152163294552893e24, -1.0153927900442934e24, -1.0178081015176163e24, -1.0181549844825729e24, -1.0200849740898955e24, -1.0211581925562546e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
- ComponentVector{Float64}(q = [-1.0110175780745738e24, -1.012518077497008e24, -1.0132323910889871e24, -1.0153993866759823e24, -1.0156683617685465e24, -1.0179917394402775e24, -1.0184170966175785e24, -1.0202951357898943e24, -1.021386989131756e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])

A Distribution Network

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.

function binary_pipe(depth::Int)
+ ComponentVector{Float64}(q = [-9.48073156362635e23, -9.494974862008833e23, -9.501182060458907e23, -9.52223803540651e23, -9.523893148908722e23, -9.546547603518372e23, -9.549801196682641e23, -9.567903566344426e23, -9.577969836073812e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-9.567887788051492e23, -9.582262025220147e23, -9.588526285340331e23, -9.609775829068942e23, -9.611446156216624e23, -9.634308874679326e23, -9.637592376561117e23, -9.655861162051317e23, -9.666019970493058e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-9.65584523484191e23, -9.670351613979445e23, -9.67667346184478e23, -9.69811835156128e23, -9.699804034878774e23, -9.722876929086521e23, -9.726190616895249e23, -9.744627346555168e23, -9.754879544942865e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-9.744611269558351e23, -9.759251004898444e23, -9.76563097144797e23, -9.787273000655262e23, -9.78897418402628e23, -9.812259183397728e23, -9.815603336932879e23, -9.834209553125829e23, -9.844556000528192e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-9.83419332547216e23, -9.848967642400913e23, -9.855406263482759e23, -9.877247242129001e23, -9.878964070809827e23, -9.90246312245073e23, -9.905838024126212e23, -9.924615283366091e23, -9.935056846759065e23], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-9.924598922232023e23, -9.939509060159686e23, -9.946006868779211e23, -9.968048635135799e23, -9.969781241985545e23, -9.993496324679988e23, -9.996902247867588e23, -1.0015852128792246e24, -1.0026389680408774e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-1.0015835612691038e24, -1.0030882819667553e24, -1.0037440361484744e24, -1.0059684759059095e24, -1.0061433291865772e24, -1.0085366388835218e24, -1.0088803621037015e24, -1.0107927708995958e24, -1.0118562131795574e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-1.0107911037014579e24, -1.0123096572553008e24, -1.0129714398272787e24, -1.0152163287476458e24, -1.0153927895437472e24, -1.0178081008084191e24, -1.0181549839488251e24, -1.0200849734376208e24, -1.021158191944551e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
+ ComponentVector{Float64}(q = [-1.0110175780743961e24, -1.012518077411929e24, -1.0132323912456828e24, -1.0153993864708337e24, -1.0156683619902203e24, -1.0179917392351275e24, -1.0184170967742698e24, -1.02029513570481e24, -1.0213869891315706e24], P = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0])

A Distribution Network

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.

function binary_pipe(depth::Int)
   s = EmbeddedDeltaSet1D{Bool,Point3D}()
   add_vertex!(s, point=Point3D(0, 0, 0))
   for n in 1:depth
@@ -254,18 +254,18 @@
  ComponentVector{Float64}(q = [4.729950388250767, -1.7816338245275347, -5.763457572042613, -11.213309507254063, -11.213309507254063, -16.66316144246551, 43.28520984486044], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
  ComponentVector{Float64}(q = [6.282779926856762, -3.3832244884695886, -10.165071397936547, -17.843588561448723, -17.843588561448723, -25.522105724960895, 58.94158307367303], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
  ComponentVector{Float64}(q = [8.880889071371024, -5.097400906159578, -15.759474281735018, -26.27060050033502, -26.27060050033502, -36.781726718935026, 78.84066168566505], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [12.638263964311724, -7.02498333524054, -22.657423091520343, -36.661181618872405, -36.661181618872405, -50.66494014622447, 103.3764036546483], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [17.835193263658514, -9.413938658192162, -31.339676930169617, -49.73951335050866, -49.73951335050866, -68.1393497708477, 134.2588508528818], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [12.63826364297635, -7.024983181721872, -22.657422532051978, -36.66118077612892, -36.66118077612892, -50.664939020205864, 103.37640166464057], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [17.83519291838076, -9.413938502930764, -31.339676371451922, -49.73951250889592, -49.73951250889592, -68.13934864633993, 134.25884886554417], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
  ⋮
- ComponentVector{Float64}(q = [8.381452128381862e23, -3.9539529121344555e23, -1.2181212477509979e24, -1.8348914997768222e24, -1.8348914997768222e24, -2.451661751802634e24, 4.3328110204814045e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [9.010695293077424e23, -4.250798589678916e23, -1.3095725209221953e24, -1.9726472150600183e24, -1.9726472150600183e24, -2.6357219091978284e24, 4.658099726318196e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [9.687179283332036e23, -4.569930144274456e23, -1.4078895559761504e24, -2.1207450273564826e24, -2.1207450273564826e24, -2.833600498736801e24, 5.007809686446823e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [1.0414450835241523e24, -4.9130207446654515e23, -1.5135878185158212e24, -2.279961397511177e24, -2.279961397511177e24, -3.046334976506518e24, 5.383774392442362e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [1.1196322758523735e24, -5.2818690902500225e23, -1.6272214450664174e24, -2.451131037505113e24, -2.451131037505113e24, -3.275040629943793e24, 5.787964886881825e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [1.2036894148165624e24, -5.67840893405189e23, -1.7493861793689867e24, -2.63515133347987e24, -2.63515133347987e24, -3.520916487590736e24, 6.22250020762894e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [1.2940572020594736e24, -6.104719281801176e23, -1.8807225161116098e24, -2.8329870812314164e24, -2.8329870812314164e24, -3.7852516463512044e24, 6.689658569966626e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [1.3912094071692127e24, -6.563035146606268e23, -2.0219190103069788e24, -3.045675471221909e24, -3.045675471221909e24, -4.0694319321368195e24, 7.191889137927369e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
- ComponentVector{Float64}(q = [1.4088358604964034e24, -6.650463565371547e23, -2.0479586893206517e24, -3.084899797837442e24, -3.084899797837442e24, -4.121840906354212e24, 7.284511287330434e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])

Multiphysics

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!

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.

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.

# μ̃ = negative viscosity per unit area
+ ComponentVector{Float64}(q = [8.381452015680874e23, -3.9539528602381576e23, -1.2181212314969948e24, -1.8348914752929442e24, -1.8348914752929442e24, -2.451661719088896e24, 4.332810962666535e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [9.010695116103939e23, -4.250798510550303e23, -1.3095724956321001e24, -1.972647176964812e24, -1.972647176964812e24, -2.635721858297526e24, 4.658099636362291e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [9.687179177668213e23, -4.5699300945770515e23, -1.4078895406342043e24, -2.120745004246463e24, -2.120745004246463e24, -2.833600467858724e24, 5.007809631876105e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [1.0414450657138773e24, -4.913020664265397e23, -1.51358779298865e24, -2.279961359058856e24, -2.279961359058856e24, -3.046334925129065e24, 5.383774301643185e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [1.1196322497700876e24, -5.2818689748174185e23, -1.627221407911169e24, -2.4511309815370813e24, -2.4511309815370813e24, -3.275040555162997e24, 5.78796475472202e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [1.2036893972876913e24, -5.6784088539045214e23, -1.7493861541446583e24, -2.63515129548373e24, -2.63515129548373e24, -3.520916436822805e24, 6.222500117906963e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [1.29405718807669e24, -6.1047192159661e23, -1.8807224958023735e24, -2.8329870506390226e24, -2.8329870506390226e24, -3.785251605475676e24, 6.689658497727445e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [1.391209397363744e24, -6.563035097684294e23, -2.0219189957930154e24, -3.045675449359104e24, -3.045675449359104e24, -4.0694319029251965e24, 7.191889086301754e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
+ ComponentVector{Float64}(q = [1.408835860476817e24, -6.650463565567887e23, -2.0479586893206772e24, -3.0848997978374805e24, -3.0848997978374805e24, -4.121840906354288e24, 7.284511287330526e24], P = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])

Multiphysics

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!

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.

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.

# μ̃ = negative viscosity per unit area
 # R = drag of pipe boundary
 # k = pressure as a function of density
 Poise = @decapode begin
@@ -290,7 +290,7 @@
 Poise = expand_operators(Poise)
 infer_types!(Poise, op1_inf_rules_1D, op2_inf_rules_1D)
 resolve_overloads!(Poise, op1_res_rules_1D, op2_res_rules_1D)
-to_graphviz(Poise)
Example block output

Then we can create the mesh and solve the equation.

sd = linear_pipe(20)
+to_graphviz(Poise)
Example block output

Then we can create the mesh and solve the equation.

sd = linear_pipe(20)
 
 sim = eval(gensim(Poise, dimension=1))
 func = sim(sd, generate)
@@ -314,12 +314,12 @@
  ComponentVector{Float64}(q = [5.029757005650556, 3.6826723340081955, 3.519880487006838, 2.9829730569719044, 3.7940556730657153, 2.8304289821119037, 3.099807347871083, 3.802838333999194, 4.721153156150058, 8.228473467120587, 8.741263167908095, 8.013090506997049, 7.04402316350908, 6.070733882467593, 5.2716375641088025, 5.047640829557176, 5.016540299282156, 5.016197047336136, 5.017187931104424], ρ = [5.0, 10.053072908678159, 6.10258425623727, 7.725537429956383, 0.5270856556470285, 5.656155644646354, 4.060517645038426, 1.2115603911157253, 3.525660818268021, 2.2597808164580515, 2.526250383398607, -1.47644167265089, -1.7936740221715506, 4.4249992808924405, 13.375591307069282, 13.199788020722718, 9.643748949378972, 6.962039237315977, 5.731748677333678, 5.0])
  ComponentVector{Float64}(q = [5.039279285279912, 3.7462332498218345, 3.5069926764145407, 3.0482558369330697, 3.70628809493424, 2.897843580417741, 3.124769232466192, 3.824194271774335, 4.862846077143543, 8.069159027298785, 8.668103512570204, 7.999550884613419, 7.052864237261939, 6.096742487878688, 5.309733600296118, 5.058883746848286, 5.018069359044691, 5.017467634310755, 5.018875961812553], ρ = [5.0, 10.953066439866772, 6.735148626201641, 9.19870987359182, 0.7884543835787208, 5.3382465424954075, 4.710267930541586, 1.228525753406003, 3.0912870305644935, 1.3255030405966475, 3.378886676186178, 1.3416740039842137, -2.7377560841166417, -0.8791441452000786, 10.264584578815674, 13.988788887998695, 11.513959279766413, 8.30250334482346, 6.505708391701816, 5.0])
  ⋮
- ComponentVector{Float64}(q = [1.2436321824948374e12, -1.7208378290664995e12, 3.85644216938103e11, 8.793441779553334e10, 3.6213026182872066e9, 5.70931394069442e6, 20.657283743915123, 6.74800716952572, 6.604264003185201, 6.922786733820706, 6.6829572669116555, 6.945403323060298, 6.711137618203906, 6.6560960822646935, 6.479123002819287, 6.147464654652452, 6.039361831653391, 5.652147818373844, 6.15427169068452], ρ = [5.0, 7.16335391127119e25, -2.748717016210192e25, -5.273962348205154e24, -2.0891685108844778e23, -3.2885865140311294e20, -8.14860850907734e14, -9466.102764559046, 147.24610772604683, 56.36699131363286, 74.77129872520672, 19.020919678521526, 7.7736205374815945, 24.81671109894143, -7.713715579502453, 9.350784798520348, -41.20554006898634, -6.373736972779067, -107.83102822686122, 5.0])
- ComponentVector{Float64}(q = [2.3868502166239204e12, -3.302730665916224e12, 7.4015050105598e11, 1.6876877874866946e11, 6.950211929764786e9, 1.0957640764807194e7, 33.66185740667038, 6.748007169665596, 6.6042640031846656, 6.922786733820655, 6.682957266911428, 6.945403323060027, 6.71113761820415, 6.6560960822644, 6.479123002819392, 6.14746465465215, 6.03936183165359, 5.652147818373393, 6.1542716906854835], ρ = [5.0, 2.6386545532679563e26, -1.0125026295144305e26, -1.9426884309762136e25, -7.695548900385527e23, -1.2113661386407464e21, -3.0015765347189855e15, -27125.196593664434, 147.2461076817767, 56.36699131381449, 74.77129872532356, 19.02091967870475, 7.773620537468033, 24.81671109898503, -7.713715579459534, 9.350784798606172, -41.20554006894829, -6.373736972630165, -107.83102822686648, 5.0])
- ComponentVector{Float64}(q = [4.579135769257439e12, -6.336238455851447e12, 1.4199674579331433e12, 3.237803307189018e11, 1.33338759362607e10, 2.1022065315628376e7, 58.59999534306588, 6.748007169878744, 6.604264003184387, 6.922786733820629, 6.68295726691131, 6.945403323059886, 6.711137618204277, 6.656096082264248, 6.479123002819446, 6.147464654651992, 6.039361831653694, 5.652147818373159, 6.154271690685985], ρ = [5.0, 9.71178461156255e26, -3.7265990142378096e26, -7.150224189310668e25, -2.832409930559836e24, -4.4585326510134437e21, -1.1047547153262946e16, -84643.0729958, 147.24610761267556, 56.36699131390908, 74.77129872538441, 19.020919678800176, 7.77362053746097, 24.81671109900774, -7.7137155794371814, 9.35078479865087, -41.20554006892847, -6.373736972552615, -107.83102822686922, 5.0])
- ComponentVector{Float64}(q = [8.782423390782506e12, -1.215240858391378e13, 2.7233862558866606e12, 6.209852896637032e11, 2.5573328922240963e10, 4.031867213607459e7, 106.4140910414971, 6.74800717023234, 6.604264003184241, 6.922786733820615, 6.682957266911249, 6.9454033230598125, 6.711137618204344, 6.656096082264168, 6.479123002819475, 6.147464654651911, 6.039361831653748, 5.652147818373036, 6.1542716906862465], ρ = [5.0, 3.5724042520517026e27, -1.3708003932449637e27, -2.6301542221607796e26, -1.041879911914887e25, -1.6400365113706342e22, -4.063753895313063e16, -281881.6406155679, 147.2461074961309, 56.36699131395837, 74.77129872541612, 19.0209196788499, 7.7736205374572895, 24.816711099019575, -7.713715579425534, 9.350784798674162, -41.20554006891815, -6.373736972512206, -107.83102822687064, 5.0])
- ComponentVector{Float64}(q = [1.6841589288493604e13, -2.3304031830366758e13, 5.2224939241773545e12, 1.190830678698961e12, 4.904062193346937e10, 7.731698683280414e7, 198.090355616631, 6.748007170855233, 6.6042640031841655, 6.922786733820608, 6.682957266911217, 6.945403323059774, 6.711137618204378, 6.656096082264126, 6.47912300281949, 6.147464654651868, 6.0393618316537765, 5.652147818372972, 6.154271690686383], ρ = [5.0, 1.3137052006569907e28, -5.040940158637577e27, -9.672050072404781e26, -3.831377871603721e25, -6.031021042205816e22, -1.4943926278056208e17, -979735.3543127471, 147.24610728869501, 56.366991313984066, 74.77129872543264, 19.02091967887582, 7.773620537455371, 24.816711099025742, -7.713715579419462, 9.350784798686304, -41.20554006891276, -6.37373697249114, -107.83102822687138, 5.0])
- 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 = [1.243633690139496e12, -1.7208399152234836e12, 3.856446844512895e11, 8.793452439767682e10, 3.6213070083617167e9, 5.709320862043918e6, 20.65730089441157, 6.748007169372349, 6.604264003199602, 6.922786733679236, 6.682957266648283, 6.945403323411019, 6.711137618488131, 6.656096081702651, 6.479123002895549, 6.147464654833385, 6.039361831672284, 5.652147818644581, 6.154271691178322], ρ = [5.0, 7.163371279569413e25, -2.7487236807611096e25, -5.273975135476635e24, -2.0891735762913375e23, -3.2885944875524935e20, -8.14862826621961e14, -9466.120477947376, 147.24610775576372, 56.36699137181165, 74.77129886036738, 19.02091962453822, 7.773620328304195, 24.816711240009546, -7.71371543499483, 9.350784748475936, -41.205540034865294, -6.373736925890354, -107.8310282236288, 5.0]) + ComponentVector{Float64}(q = [2.38685312611806e12, -3.302734691839368e12, 7.401514032757903e11, 1.6876898447241855e11, 6.9502204018513975e9, 1.095765412181882e7, 33.6618905037668, 6.748007169512225, 6.6042640031990665, 6.922786733679185, 6.682957266648056, 6.945403323410748, 6.711137618488375, 6.656096081702358, 6.479123002895654, 6.147464654833083, 6.039361831672483, 5.6521478186441305, 6.154271691179286], ρ = [5.0, 2.6386609861695205e26, -1.0125050979427956e26, -1.942693167148843e25, -7.695567661730703e23, -1.2113690918880587e21, -3.0015838524040735e15, -27125.252296471215, 147.24610771149355, 56.36699137199328, 74.77129886048422, 19.020919624721444, 7.773620328290633, 24.81671124005315, -7.7137154349519115, 9.350784748561761, -41.205540034827244, -6.373736925741452, -107.83102822363406, 5.0]) + ComponentVector{Float64}(q = [4.579141102791206e12, -6.336245835964959e12, 1.4199691118357058e12, 3.2378070784101074e11, 1.3333891466850594e10, 2.1022089801010452e7, 58.60005601458726, 6.748007169725374, 6.604264003198788, 6.922786733679159, 6.6829572666479375, 6.9454033234106065, 6.711137618488502, 6.656096081702205, 6.479123002895708, 6.147464654832926, 6.039361831672587, 5.652147818643896, 6.154271691179788], ρ = [5.0, 9.711807234775703e26, -3.7266076952009026e26, -7.150240845471457e25, -2.832416528545328e24, -4.4585430369869334e21, -1.104757288807992e16, -84643.25075109309, 147.24610764239236, 56.366991372087874, 74.77129886054507, 19.02091962481687, 7.77362032828357, 24.816711240075858, -7.713715434929559, 9.35078474860646, -41.20554003480743, -6.373736925663902, -107.8310282236368, 5.0]) + ComponentVector{Float64}(q = [8.782433043865277e12, -1.215242194107052e13, 2.7233892492600913e12, 6.209859722113494e11, 2.557335703082268e10, 4.031871645180142e7, 106.41420084962785, 6.748007170078969, 6.604264003198642, 6.922786733679145, 6.682957266647876, 6.945403323410533, 6.711137618488569, 6.656096081702125, 6.4791230028957365, 6.147464654832844, 6.039361831672641, 5.6521478186437735, 6.154271691180049], ρ = [5.0, 3.5724121049790595e27, -1.3708034065646116e27, -2.630160003816089e26, -1.0418822021952408e25, -1.6400401165303285e22, -4.06376282833454e16, -281882.22482422146, 147.24610752584758, 56.366991372137164, 74.77129886057678, 19.020919624866593, 7.77362032827989, 24.81671124008769, -7.713715434917911, 9.350784748629751, -41.2055400347971, -6.373736925623493, -107.83102822363823, 5.0]) + ComponentVector{Float64}(q = [1.6841607938726861e13, -2.330405763705409e13, 5.222499707522854e12, 1.1908319974147607e12, 4.904067624063912e10, 7.731707245298092e7, 198.09056777097626, 6.748007170701864, 6.604264003198566, 6.922786733679138, 6.682957266647844, 6.945403323410495, 6.711137618488603, 6.656096081702083, 6.479123002895752, 6.147464654832802, 6.03936183167267, 5.6521478186437095, 6.154271691180186], ρ = [5.0, 1.3137081102184527e28, -5.04095132318591e27, -9.672071493819623e26, -3.8313863572436998e25, -6.03103439956072e22, -1.494395937549117e17, -979737.4554640319, 147.2461073184115, 56.36699137216286, 74.7712988605933, 19.020919624892514, 7.773620328277971, 24.81671124009386, -7.7137154349118395, 9.350784748641892, -41.20554003479172, -6.373736925602427, -107.83102822363897, 5.0]) + ComponentVector{Float64}(q = [3.2293656770145098e13, -4.4685355545744e13, 1.0014103977207451e13, 2.28341141441807e12, 9.403512881653964e10, 1.482549062176478e8, 373.86385626956854, 6.7480071718410874, 6.6042640031985265, 6.922786733679135, 6.682957266647827, 6.945403323410474, 6.711137618488621, 6.656096081702062, 6.47912300289576, 6.147464654832779, 6.039361831672685, 5.652147818643677, 6.154271691180257], ρ = [5.0, 4.83021184230523e28, -1.8534454182496424e28, -3.55620505863349e27, -1.4087153470875939e26, -2.2174769084556927e23, -5.4945607239134746e17, -3.4929404892836628e6, 147.24610693675038, 56.36699137217626, 74.77129886060193, 19.020919624906032, 7.773620328276971, 24.816711240097074, -7.713715434908673, 9.350784748648223, -41.20554003478891, -6.373736925591443, -107.83102822363935, 5.0]) + ComponentVector{Float64}(q = [6.191999409058388e13, -8.567988974014819e13, 1.920108532504286e13, 4.3782227052262793e12, 1.8030335412389993e11, 2.8426458532630205e8, 710.8753931541236, 6.748007173970266, 6.604264003198506, 6.922786733679133, 6.6829572666478185, 6.9454033234104635, 6.711137618488631, 6.65609608170205, 6.479123002895764, 6.147464654832768, 6.039361831672693, 5.652147818643659, 6.154271691180294], ρ = [5.0, 1.775797801316619e29, -6.814078566456068e28, -1.3074170099557157e28, -5.179055698640534e26, -8.152418078719469e23, -2.0200416136330243e18, -1.2631612426516624e7, 147.2461062210701, 56.36699137218324, 74.77129886060642, 19.02091962491308, 7.77362032827645, 24.81671124009875, -7.713715434907022, 9.350784748651526, -41.20554003478745, -6.373736925585715, -107.83102822363955, 5.0]) + ComponentVector{Float64}(q = [1.1872285355971111e14, -1.6427910163947972e14, 3.681537239681296e13, 8.394624398101236e12, 3.4570624638127905e11, 5.450372427369486e8, 1357.03083286261, 6.748007177997497, 6.604264003198495, 6.922786733679132, 6.682957266647814, 6.945403323410458, 6.711137618488636, 6.656096081702044, 6.479123002895766, 6.147464654832762, 6.0393618316726965, 5.65214781864365, 6.1542716911803135], ρ = [5.0, 6.52830355470577e29, -2.5050359502990984e29, -4.806411578714249e28, -1.903958193203204e27, -2.997045046915328e24, -7.426208617858688e18, -4.603424111164324e7, 147.24610486498193, 56.36699137218689, 74.77129886060877, 19.020919624916758, 7.773620328276178, 24.816711240099625, -7.713715434906161, 9.350784748653247, -41.20554003478669, -6.373736925582727, -107.83102822363965, 5.0]) + ComponentVector{Float64}(q = [2.2763150359973322e14, -3.149781006366149e14, 7.058740859946292e13, 1.6095308667879084e13, 6.628347479903256e11, 1.0450190816909521e9, 2595.910663185995, 6.748007185663883, 6.60426400319849, 6.922786733679131, 6.682957266647811, 6.9454033234104555, 6.711137618488639, 6.656096081702041, 6.479123002895767, 6.147464654832758, 6.039361831672698, 5.652147818643646, 6.154271691180323], ρ = [5.0, 2.3999180582677308e30, -9.208948333003705e29, -1.766920586130701e29, -6.999281838196946e27, -1.1017659456029173e25, -2.7300036080243507e19, -1.684570340152366e8, 147.24610228103944, 56.366991372188785, 74.77129886060999, 19.020919624918676, 7.773620328276036, 24.81671124010008, -7.713715434905712, 9.350784748654146, -41.20554003478629, -6.373736925581169, -107.8310282236397, 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 a5ca73ac..02ea3dd1 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, ax, ob = wireframe(plot_mesh)\nax.aspect = AxisAspect(3.0)\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, ax, ob = mesh(plot_mesh; color=c[1:nv(plot_mesh)])\nax.aspect = AxisAspect(3.0)\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, ax, ob = mesh(plot_mesh, color=colors[1], colorrange = extrema(vcat(colors...)))\nax.aspect = AxisAspect(3.0)\nColorbar(fig[1,2], ob)\nframerate = 30\n\n# Animation\nrecord(fig, \"diff_adv_right.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\n ob.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\nwf = wireframe(s)\nsave(\"ice_mesh.png\", wf)","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 = mesh(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\n# This sharp operator, ♯, is scheduled to be upstreamed.\ninclude(\"sharp_op.jl\")\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n # We pre-allocate matrices that encode differential operators.\n ♯_m = ♯_mat(sd)\n op = @match my_symbol begin\n :♯ => x -> begin\n ♯_m * x\n end\n :mag => x -> begin\n norm.(x)\n end\n :^ => (x,y) -> begin\n x .^ y\n end\n x => error(\"Unmatched operator $my_symbol\")\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)\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)\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)\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, ax, ob = mesh(s′, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(tₑ).dynamics_h))\n Colorbar(fig[1,2], ob)\n record(fig, \"ice_dynamics_cism.gif\", range(0.0, tₑ; length=frames); framerate = 30) do t\n ob.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":"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 hundred 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₀))\ndisplay(fig)","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())\nwireframe(s)","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)","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:\nmesh(s′, color=soln(tₑ).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))","category":"page"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"begin\n frames = 100\n fig, ax, ob = CairoMakie.mesh(s′, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\n Colorbar(fig[1,2], ob)\n record(fig, \"ice_dynamics2D.gif\", range(0.0, tₑ; length=frames); framerate = 15) do t\n ob.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.\nmesh(s′, color=h₀, colormap=:jet)","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":"mesh(s′, color=soln(tₑ).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))","category":"page"},{"location":"ice_dynamics/","page":"Glacial Flow","title":"Glacial Flow","text":"begin\n frames = 200\n fig, ax, ob = CairoMakie.mesh(s′, color=soln(0).dynamics_h, colormap=:jet, colorrange=extrema(soln(0).dynamics_h))\n\n Colorbar(fig[1,2], ob)\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 ob.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())\nwireframe(s)","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,a,o = mesh(s′, color=h, colormap=:jet,\n colorrange=extrema(soln(tₑ).h);\n axis = (; title = @lift(\"Grigoriev Ice Cap Dynamic Thickness [m] at time $($time))\")))\n Colorbar(f[1,2], limits=extrema(soln(0.0).h), colormap=:jet)\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(resolution = (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(resolution = (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, ax, ob = wireframe(plot_mesh)\nax.aspect = AxisAspect(3.0)\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, ax, ob = mesh(plot_mesh; color=c[point_map])\nax.aspect = AxisAspect(3.0)\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, ax, ob = mesh(plot_mesh, color=colors[1], colorrange = extrema(vcat(colors...)))\nax.aspect = AxisAspect(3.0)\nColorbar(fig[1,2], ob)\nframerate = 30\n\n# Animation\nrecord(fig, \"diffusion.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\nob.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, ax, ob = mesh(plot_mesh, color=colors[1], colorrange = extrema(vcat(colors...)))\nax.aspect = AxisAspect(3.0)\nColorbar(fig[1,2], ob)\nframerate = 30\n\n# Animation\nrecord(fig, \"diff_adv.gif\", range(0.0, 100.0; length=150); framerate = 30) do t\nob.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 are a graphical tool for the composition of physical systems. Ultimately, this library will include tooling which takes advantage of the formalization of physical theories described by DEC provided by CombinatorialSpaces.jl.","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","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])\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])\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\n# This sharp operator, ♯, is scheduled to be upstreamed.\ninclude(\"sharp_op.jl\")\nfunction generate(sd, my_symbol; hodge=GeometricHodge())\n # We pre-allocate matrices that encode differential operators.\n ♯_m = ♯_mat(sd)\n op = @match my_symbol begin\n :♯ => x -> begin\n ♯_m * x\n end\n :mag => x -> begin\n norm.(x)\n end\n :^ => (x,y) -> begin\n x .^ y\n end\n x => error(\"Unmatched operator $my_symbol\")\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)\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)\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)\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])\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":"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 hundred 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₀))\ndisplay(fig)","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())\nwireframe(s)","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)","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))\ndisplay(fig)","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)\ndisplay(fig)","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))\ndisplay(fig)","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 are a graphical tool for the composition of physical systems. Ultimately, this library will include tooling which takes advantage of the formalization of physical theories described by DEC provided by CombinatorialSpaces.jl.","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","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"}] }