diff --git a/docs/make.jl b/docs/make.jl index bcc9f8f6..1cf1da9d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -105,7 +105,8 @@ makedocs(; "19 - Jura tutorial" => "man/Tutorial_Jura.md", "20 - 2D model setups" => "man/Tutorial_NumericalModel_2D.md", "21 - 3D model setups" => "man/Tutorial_NumericalModel_3D.md", - "22 - Build geometry from polygons" => "man/tutorial_Polygon_structures.md" + "22 - 3D model setups" => "man/Tutorial_VolcanoModel_3D.md", + "23 - Build geometry from polygons" => "man/tutorial_Polygon_structures.md" ], "User Guide" => Any[ "Installation" => "man/installation.md", diff --git a/docs/src/assets/img/VolcanoModel3D.png b/docs/src/assets/img/VolcanoModel3D.png new file mode 100644 index 00000000..f3660658 Binary files /dev/null and b/docs/src/assets/img/VolcanoModel3D.png differ diff --git a/docs/src/man/Tutorial_VolcanoModel_3D.md b/docs/src/man/Tutorial_VolcanoModel_3D.md new file mode 100644 index 00000000..aaf05a2f --- /dev/null +++ b/docs/src/man/Tutorial_VolcanoModel_3D.md @@ -0,0 +1,81 @@ +# Simple model of a magmatic chamber with a volcano on top + +### Aim +The aim of this tutorial is to show you how to create 3D numerical model setups that can be used as initial setups for other codes. + +### Generating the model + +Lets start with creating a 3D model setup in cartesian coordinates, which uses the `CartData` data structure, with a resolution of $ 128 \times 128 \times 128 $ grid points, inside the domain $\Omega \in [-100,100] \times [-100,100] \times [-110,50]$ km + +```julia +using GeophysicalModelGenerator + +nx,ny,nz = 128, 128, 128 +x = range(-100, 100, nx); +y = range(-100, 100, ny); +z = range(-110, 50, nz); +Grid = CartData(xyz_grid(x,y,z)); +``` + +Now we create an integer array that will hold the `Phases` information (which usually refers to the material or rock type in the simulation) + +```julia +Phases = fill(0, nx, ny, nz); +``` + +And as in the previous tutorials we initialize the temperature field: +```julia +Temp = fill(1350.0, nx,ny,nz); +``` + +For simplicity, we will assume a model with thee horizontal layers with different rheology where later we add the volcano and the magmatic chamber. We use `add_box!` to generate the initial horizontally layered model: + +```julia +lith = LithosphericPhases(Layers=[15 45 100], Phases=[1 2 3]) +add_box!(Phases, Temp, Grid; + xlim=(-100, 100), + ylim=(-400, 400.0), + zlim=(-110.0, 0.0), + phase = lith, + T = HalfspaceCoolingTemp(Age=20) +) +``` + +Then we can add the volcanic shape using `add_volcano!` function. In this case the base of the volcano will be centered at $x_i = (0,0,0)$, with a height of 10 km and a 15 km radius: +```julia +add_volcano!(Phases, Temp, Grid; + volcanic_phase = 1, + center = (0, 0, 0), + height = 10, + radius = 15, + base = 0.0, + background = nothing, + T = HalfspaceCoolingTemp(Age=20) +) +``` +We can also add a magmatic chamber located below the volcano +```julia +add_ellipsoid!(Phases, Temp, Grid; + cen = (0, 0, -40), + axes = (10, 10, 10), + phase = ConstantPhase(4), +) +``` + +where we prescribe a constant temperature of $T=1400^{\circ}C$ +```julia +@. Temp[Phases == 4] = 1400 +Grid = addfield(Grid, (;Phases, Temp)) +``` + +Finally we setup the temperature of the air to $T^{\text{air}}=0^{\circ}C$ +```julia +@. Temp[Phases == 0] = 0 +``` + +```julia +write_paraview(Grid,"VolcanoModel3D"); +``` + +And the resulting image looks like +![Mechanical3D_Tutorial_2](../assets/img/VolcanoModel3D.png) diff --git a/src/Setup_geometry.jl b/src/Setup_geometry.jl index 02b5b7e7..ea7e420c 100644 --- a/src/Setup_geometry.jl +++ b/src/Setup_geometry.jl @@ -11,7 +11,7 @@ import Base: show # These are routines that help to create input geometries, such as slabs with a given angle # -export add_box!, add_sphere!, add_ellipsoid!, add_cylinder!, add_layer!, add_polygon!, add_slab!, add_stripes!, +export add_box!, add_sphere!, add_ellipsoid!, add_cylinder!, add_layer!, add_polygon!, add_slab!, add_stripes!, add_volcano!, make_volc_topo, ConstantTemp, LinearTemp, HalfspaceCoolingTemp, SpreadingRateTemp, LithosphericTemp, LinearWeightedTemperature, McKenzie_subducting_slab, @@ -685,7 +685,82 @@ function Rot3D(X::_T,Y::_T,Z::_T, cosStrikeAngle::_T, sindStrikeAngle::_T, cosDi return CoordRot[1], CoordRot[2], CoordRot[3] end +""" +add_volcano!( + Phases, Temp, Grid::CartData; + volcanic_phase, + center, + height, + radius, + crater, + base, + background, + T, +) + +Adds a volcano topography (cones and truncated cones) + +Parameters +==== +- Phases - Phase array (consistent with Grid) +- Temp - Temperature array (consistent with Grid) +- Grid - CartData + +Optional Parameters +==== +- volcanic_phase - phase number of the volcano, +- center - x- and -coordinates of center of volcano +- height - height of volcano +- radius - radius of volcano +- T - temperature structure of the volcano +- crater - this will create a truncated cone and the option defines the radius of the flat top +- base - this sets the flat topography around the volcano +- background - this allows loading in a topography and only adding the volcano on top (also allows stacking of several cones to get a volcano with different slopes) +""" +function add_volcano!( + Phases, + Temp, + Grid::CartData; + volcanic_phase = 1, + center = (0,0,0), + height = 0.0, + radius = 0.0, + crater = 0.0, + base = 0.0, + background = nothing, + T = HalfspaceCoolingTemp(Age=0) +) + H = make_volc_topo(Grid; + center = center, + height = height, + radius = radius, + crater = crater, + base = base, + background = background + ) + + ni = size(Grid.x) + ind = fill(false, ni...) + depth = similar(Grid.z.val) + + for k in axes(ind, 3) + for j in axes(ind, 2), i in axes(ind, 1) + depth[i, j, k] = max(H[i, j] - Grid.z.val[i, j, k], 0) + + if Grid.z.val[i, j, k] < H[i, j] && Grid.z.val[i, j, k] ≥ base + Phases[i, j, k] = volcanic_phase + end + if Phases[i, j, k] > 0 + ind[i, j, k] = true + end + end + end + # @views Temp[ind .== false] .= 0.0 + @views Temp[ind] .= compute_thermal_structure(Temp[ind], Grid.x.val[ind], Grid.y.val[ind], depth[ind], Phases[ind], T) + + return nothing +end """ make_volc_topo(Grid::LaMEM_grid; center::Array{Float64, 1}, height::Float64, radius::Float64, crater::Float64, @@ -797,6 +872,61 @@ function make_volc_topo(Grid::LaMEM_grid; return CartData(reshape(X,nx,ny,1), reshape(Y,nx,ny,1), reshape(Topo,nx,ny,1), (Topography=reshape(Topo,nx,ny,1),)) end +function make_volc_topo(Grid::CartData; + center = (0,0,0), + height = 0.0, + radius = 0.0, + crater = 0.0, + base = 0.0, + background = nothing +) + # get node grid + X = @views Grid.x.val[:,:,1] + Y = @views Grid.y.val[:,:,1] + nx = size(X, 1) + ny = size(X, 2) + pos = similar(X) + + for i in eachindex(pos) + # compute radial distance to volcano center + DX = X[i] - center[1] + DY = Y[i] - center[2] + RD = √(DX^2 + DY^2) + + # get radial distance from crater rim + RD -= crater + + # find position relative to crater rim + dr = radius - crater + pos[i] = -RD / dr + 1 + end + + ## assign topography + H = zeros(nx,ny) + # check if there is a background supplied + if background === nothing + H .= base + else + # background = nondimensionalize(background, CharUnits) + if size(background) == size(X) + H .= background + elseif size(background) == size(reshape(X,nx,ny,1)) + H .= @views background[:,:,1] + else + error("Size of background must be ", nx, "x", ny) + end + end + + for i in eachindex(pos) + if 0 ≤ pos[i] < 1 + H[i] = pos[i] * (height - base) + base + elseif pos[i] ≥ 1 + H[i] = height + end + end + + return H +end abstract type AbstractThermalStructure end diff --git a/volcano.jl b/volcano.jl new file mode 100644 index 00000000..0a3cec6e --- /dev/null +++ b/volcano.jl @@ -0,0 +1,66 @@ +using GeophysicalModelGenerator + +nx,ny,nz = 128, 128, 128 +x = range(-100, 100, nx); +y = range(-100, 100, ny); +z = range(-110, 50, nz); +Grid = CartData(xyz_grid(x,y,z)); + +# Now we create an integer array that will hold the `Phases` information (which usually refers to the material or rock type in the simulation) +Phases = fill(0, nx, ny, nz); + +# In many (geodynamic) models, one also has to define the temperature, so lets define it as well +Temp = fill(1350.0, nx, ny, nz); + +lith = LithosphericPhases(Layers=[15 45 100], Phases=[1 2 3]) + +# And an an inclined part: +add_box!(Phases, Temp, Grid; + xlim=(-100, 100), + ylim=(-400, 400.0), + zlim=(-110.0, 0.0), + phase = lith, + Origin = (0,0,0), + T = HalfspaceCoolingTemp(Age=20)); + + +# # Add them to the `CartData` dataset: +Grid = addfield(Grid,(;Phases, Temp)) +# heatmap(x, z, Grid.fields.Temp[:,64,:]) + +# Which looks like +# write_paraview(Grid,"Grid3D_FreeSubduction"); + +center = (0, 0, 0) +height = 10 # [kmm] +radius = 15 # [km] +crater = 0.0 +base = 0.0 +background = nothing + + +add_volcano!(Phases, Temp, Grid; + volcanic_phase = 1, + center = (0, 0, 0), + height = 10, + radius = 15, + crater = 0.0, + base = 0.0, + background = nothing, + T = HalfspaceCoolingTemp(Age=20) +) + +Grid = addfield(Grid,(;Phases, Temp)) + +write_paraview(Grid,"Grid3D_FreeSubduction"); + +Grid.fields.Temp[Grid.fields.Temp.==0] .= NaN +heatmap(x, z, Grid.fields.Temp[:,64,:]) +heatmap(x, z, Grid.fields.Phases[:,64,:]) + +# heatmap(x, z, Grid.z.val[:,64,:]) +# heatmap(x, z, depth[:,64,:]) +# heatmap(x, z, ind[:,64,:]) + +# Grid.fields.Temp[:,:,1] +# lines(Grid.fields.Temp[64,64,:], depth[64,64,:])