From d5ab3916e7371fb4814c1d5d4711087ebd694f03 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 7 Mar 2024 16:38:55 +0100 Subject: [PATCH 01/60] subduction3D miniapp --- src/stokes/Stokes3D.jl | 32 ++-- subduction/GMG_setup.jl | 77 ++++++++ subduction/Subduction3D.jl | 304 ++++++++++++++++++++++++++++++ subduction/Subduction_rheology.jl | 77 ++++++++ 4 files changed, 472 insertions(+), 18 deletions(-) create mode 100644 subduction/GMG_setup.jl create mode 100644 subduction/Subduction3D.jl create mode 100644 subduction/Subduction_rheology.jl diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index d976983d..55d141a9 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -14,7 +14,6 @@ using GeoParams import JustRelax: PTArray, Velocity, SymmetricTensor, pureshear_bc! import JustRelax: Residual, StokesArrays, PTStokesCoeffs, AbstractStokesModel, ViscoElastic, IGG -import JustRelax: tensor_invariant!, compute_τ_nonlinear!, compute_τ_vertex!, compute_τ! import JustRelax: compute_maxloc!, solve! import JustRelax: mean_mpi, norm_mpi, minimum_mpi, maximum_mpi, backend @@ -27,9 +26,6 @@ include("PressureKernels.jl") include("VelocityKernels.jl") export solve!, pureshear_bc! -rotate_stress_particles_jaumann!, -rotate_stress_particles_rotation_matrix!, compute_vorticity!, -tensor_invariant! @parallel function update_τ_o!( τxx_o, τyy_o, τzz_o, τxy_o, τxz_o, τyz_o, τxx, τyy, τzz, τxy, τxz, τyz @@ -443,20 +439,20 @@ function JustRelax.solve!( stokes.∇V, @strain(stokes)..., @velocity(stokes)..., _di... ) - # Update buoyancy - @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, args) - - # Update viscosity - ν = 1e-2 - @parallel (@idx ni) compute_viscosity!( - η, - ν, - phase_ratios.center, - @strain(stokes)..., - args, - rheology, - viscosity_cutoff, - ) + # # Update buoyancy + # @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, args) + + # # Update viscosity + # ν = 1e-2 + # @parallel (@idx ni) compute_viscosity!( + # η, + # ν, + # phase_ratios.center, + # @strain(stokes)..., + # args, + # rheology, + # viscosity_cutoff, + # ) @parallel (@idx ni) compute_τ_nonlinear!( @tensor_center(stokes.τ), diff --git a/subduction/GMG_setup.jl b/subduction/GMG_setup.jl new file mode 100644 index 00000000..77c67ee6 --- /dev/null +++ b/subduction/GMG_setup.jl @@ -0,0 +1,77 @@ +#= +# 3D Subduction example + +This is a 3D subduction example for `LaMEM.jl` that illustrates how to use the julia interface. +This is very similar to the setup described by Schellart and coworkers in a [2007 nature paper](https://www.nature.com/articles/nature05615) in which they demonstrate that toroidal flow changes the slab curvature during subduction as a function of slab width. +=# + +# ## 1. Generate main model setup +# We first load the packages: +using LaMEM, GeophysicalModelGenerator + +function generate_model() + # Next, we generate the main model setup, specifying the resolution and grid dimensions. + # Note that a range of default values will be set, depending on the parameters you specify. + model = Model( + ## Define the grid + # Grid(nel=(128,32,64), x=[-3960, 500], y=[0,2640], z=[-660 ,0]), + Grid(nel=(43,12,22), x=[-3960, 500], y=[0,2640], z=[-660 ,0]), + + ## No slip lower boundary; the rest is free slip + BoundaryConditions(noslip = [0, 0, 0, 0, 1, 0]), + + ## We use a multigrid solver with 4 levels: + Solver(SolverType="multigrid", MGLevels=1, MGCoarseSolver="mumps", + PETSc_options=[ "-snes_type ksponly", + "-js_ksp_rtol 1e-3", + "-js_ksp_atol 1e-4", + "-js_ksp_monitor"]), + + ## Output filename + Output(out_file_name="Subduction_3D", out_dir="Subduction_3D"), + + ## Timestepping etc + Time(nstep_max=200, nstep_out=5, time_end=100, dt_min=1e-5), + + ## Scaling: + Scaling(GEO_units(length=1km, stress=1e9Pa) ) + ) + + + # ## 2. Define geometry + # Next, we specify the geometry of the model, using the `AddBox!` function from `GeophysicalModelGenerator`. + # We start with the horizontal part of the slab. The function `AddBox!` allows you to specify a layered lithosphere; here we have a crust and mantle. It also allows specifying a thermal structure. + # Since the current setup is only mechanical, we don't specify that here. + AddBox!(model, xlim=(-3000,-1000), ylim=(0,1000), zlim=(-80,0), phase=LithosphericPhases(Layers=[20,60], Phases=[1,2])) + + # The inclined part of the slab is generate by giving it a dip: + AddBox!(model, xlim=(-1000,-810), ylim=(0,1000), zlim=(-80,0), phase=LithosphericPhases(Layers=[20,60], Phases=[1,2]), DipAngle=16) + + # There is a simple way to have a quick look at this setup by using the `Plots.jl` package: + # using Plots + # plot_cross_section(model, y=100, field=:phase) + + # Which will give the following plot: + # ![2D cross section](assets/SubductionSetup_3D.png) + + # ## 3. Add material properties: + # We can specify material properties by using the `Phase` function + mantle = Phase(Name="mantle",ID=0,eta=1e21,rho=3200) + crust = Phase(Name="crust", ID=1,eta=1e21,rho=3280) + slab = Phase(Name="slab", ID=2,eta=2e23,rho=3280) + + # and we can add them to the model with: + add_phase!(model, mantle, slab, crust) + + T = model.Grid.Temp + phases = model.Grid.Phases + + # ni = 128,32,64 + x = -3960e3, 500e3 + y = 0, 2640e3 + z = -660e3, 0 + li = abs(x[2]-x[1]), abs(y[2]-y[1]), abs(z[2]-z[1]) + origin = x[1], y[1], z[1] + + return li, origin, T, phases +end diff --git a/subduction/Subduction3D.jl b/subduction/Subduction3D.jl new file mode 100644 index 00000000..21543684 --- /dev/null +++ b/subduction/Subduction3D.jl @@ -0,0 +1,304 @@ +using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +@init_parallel_stencil(Threads, Float64, 3) + +using JustPIC +using JustPIC._3D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +# model = PS_Setup(:Threads, Float64, 3) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:CUDA, Float64, 3) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("Subduction_rheology.jl") +include("GMG_setup.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[3] +macro all_k(A) + esc(:($A[$idx_k])) +end + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) + + li, origin, T_GMG, phases_GMG = generate_model() + + # Physical domain ------------------------------------ + # lz = 700e3 # domain length in z + # lx = ly = lz # domain length in x and y + ni = nx, ny, nz # number of cells + di = @. li / ni # grid steps + # origin = 0.0, 0.0, -lz # origin coordinates (15km of sticky air layer) + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies() + dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell, max_xcell, min_xcell = 25, 35, 8 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi..., di..., ni... + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vx, grid_vy, grid_vz = velocity_grids(xci, xvi, di) + # temperature + pPhases, = init_cell_arrays(particles, Val(1)) + particle_args = (pPhases, ) + + # Assign particles phases anomaly + init_phases!(pPhases, PTArray(phases_GMG), particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.5 / √3.1) + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + thermal = ThermalArrays(ni) + # thermal_bc = TemperatureBoundaryConditions() + # thermal.T .= T_GMG + # @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(3)) + @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, (T=thermal.Tc, P=stokes.P)) + @parallel init_P!(stokes.P, ρg[3], xci[3]) + # Rheology + η = @ones(ni...) + args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + @parallel (@idx ni) compute_viscosity!( + η, 1.0, phase_ratios.center, @strain(stokes)..., args, rheology, (1e18, 1e24) + ) + η_vep = deepcopy(η) + + # # PT coefficients for thermal diffusion + # pt_thermal = PTThermalCoeffs( + # rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 + # ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), + no_slip = (left = false, right = false, top = false, bot = false, front = false, back = false), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(stokes.V.Vx, stokes.V.Vy, stokes.V.Vz) + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = figdir*"\\vtk" + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + # # Plot initial T and η profiles + # fig = let + # Zv = [z for x in xvi[1], y in xvi[2], z in xvi[3]][:] + # Z = [z for x in xci[1], y in xci[2], z in xci[3]][:] + # fig = Figure(size = (1200, 900)) + # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + # lines!(ax1, Array(thermal.T[:]), Zv./1e3) + # lines!(ax2, Array(log10.(η[:])), Z./1e3) + # ylims!(ax1, minimum(xvi[3])./1e3, 0) + # ylims!(ax2, minimum(xvi[3])./1e3, 0) + # hideydecorations!(ax2) + # save(joinpath(figdir, "initial_profile.png"), fig) + # fig + # end + + # grid2particle!(pT, xvi, thermal.T, particles) + # dt₀ = similar(stokes.P) + + local Vx_v, Vy_v, Vz_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + Vz_v = @zeros(ni.+1...) + end + # Time loop + t, it = 0.0, 0 + while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # # interpolate fields from particle to grid vertices + # particle2grid!(thermal.T, pT, xvi, particles) + # temperature2center!(thermal) + + # Update buoyancy and viscosity - + args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + @parallel (@idx ni) compute_viscosity!( + η, 1.0, phase_ratios.center, @strain(stokes)..., args, rheology, (1e18, 1e24) + ) + @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, args) + + # Stokes solver ---------------- + solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + Inf, + igg; + iterMax = 100e3, + nout = 1e3, + viscosity_cutoff = (1e18, 1e24) + ); + @parallel (JustRelax.@idx ni) JustRelax.Stokes3D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + dt = compute_dt(stokes, di, dt_diff) / 2 + # ------------------------------ + + # # Thermal solver --------------- + # heatdiffusion_PT!( + # thermal, + # pt_thermal, + # thermal_bc, + # rheology, + # args, + # dt, + # di; + # igg = igg, + # phase = phase_ratios, + # iterMax = 10e3, + # nout = 1e2, + # verbose = true, + # ) + # subgrid_characteristic_time!( + # subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + # ) + # centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + # subgrid_diffusion!( + # pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + # ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection_RK!(particles, @velocity(stokes), grid_vx, grid_vy, grid_vz, dt, 2 / 3) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject = check_injection(particles) + inject && inject_particles_phase!(particles, pPhases, tuple(), tuple(), xvi) + # update phase ratios + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 1) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, Vz_v, @velocity(stokes)...) + data_v = (; + T = Array(thermal.T), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + ) + data_c = (; + Tc = Array(thermal.Tc), + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(log10.(η)), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c + ) + end + + slice_j = ny >>> 1 + # Make Makie figure + fig = Figure(size = (1400, 1800), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "P [GPA] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "τII [MPa]") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[3].*1e-3, Array(stokes.P[:, slice_j, :]./1e9) , colormap=:lajolla) + # Plot particles phase + h2 = heatmap!(ax2, xci[1].*1e-3, xci[3].*1e-3, Array(stokes.τ.II[:, slice_j, :].*1e-6) , colormap=:batlow) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(stokes.ε.II[:, slice_j, :])) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(η[:, slice_j, :])) , colormap=:batlow) + hideydecorations!(ax3) + hideydecorations!(ax4) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end +## END OF MAIN SCRIPT ---------------------------------------------------------------- + +do_vtk = true # set to true to generate VTK files for ParaView +nx = 126 +ny = 33 +nz = 63 +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, nz; init_MPI= true)...) +else + igg +end + +# (Path)/folder where output data and figures are stored +figdir = "Subduction3D" +# main3D(igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); \ No newline at end of file diff --git a/subduction/Subduction_rheology.jl b/subduction/Subduction_rheology.jl new file mode 100644 index 00000000..27435edc --- /dev/null +++ b/subduction/Subduction_rheology.jl @@ -0,0 +1,77 @@ +# from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 + +function init_rheologies() + # Define rheolgy struct + rheology = ( + # Name = "mantle", + SetMaterialParams(; + Phase = 1, + Density = ConstantDensity(; ρ=3.2e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "crust", + SetMaterialParams(; + Phase = 2, + Density = ConstantDensity(; ρ=3.28e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "slab", + SetMaterialParams(; + Phase = 3, + Density = ConstantDensity(; ρ=3.28e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + + @parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + iszero(JustRelax.@cell(index[ip, I...])) && continue + + pᵢ = ntuple(Val(N)) do i + JustRelax.@cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1, offk in 0:1 + ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + !(kk ≤ ni[3]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + xvi[3][kk], + ) + # @show xvᵢ ii jj kk + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj, kk] + end + end + JustRelax.@cell phases[ip, I...] = particle_phase + 1.0 + + end + return nothing + end + + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + From 3ca4dd5e4fac5364d2ae4efa44754b8e9afe24f0 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Mon, 11 Mar 2024 18:00:44 +0100 Subject: [PATCH 02/60] improve VTK writter --- src/IO/VTK.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/IO/VTK.jl b/src/IO/VTK.jl index 10dae088..8680b90c 100644 --- a/src/IO/VTK.jl +++ b/src/IO/VTK.jl @@ -41,7 +41,7 @@ function append!(data_series, data::NamedTuple, time_step, seconds) return nothing end -function save_vtk(fname::String, xvi, xci, data_v::NamedTuple, data_c::NamedTuple) +function save_vtk(fname::String, xvi, xci, data_v::NamedTuple, data_c::NamedTuple, velocity::NTuple{N, T}) where {N, T} # unpack data names and arrays data_names_v = string.(keys(data_v)) @@ -49,6 +49,11 @@ function save_vtk(fname::String, xvi, xci, data_v::NamedTuple, data_c::NamedTupl data_names_c = string.(keys(data_c)) data_arrays_c = values(data_c) + velocity_field = rand(N, size(first(velocity))...) + for (i, v) in enumerate(velocity) + velocity_field[i, :, :, :] = v + end + vtk_multiblock(fname) do vtm # First block. # Variables stores in cell centers @@ -63,6 +68,7 @@ function save_vtk(fname::String, xvi, xci, data_v::NamedTuple, data_c::NamedTupl for (name_i, array_i) in zip(data_names_v, data_arrays_v) vtk[name_i] = Array(array_i) end + vtk["Velocity"] = velocity_field end end From 5b0c22b19708d260f33124b761d85f509e3c2233 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Mon, 11 Mar 2024 18:00:59 +0100 Subject: [PATCH 03/60] fix 3D velocity interpolation to vertices --- src/Interpolations.jl | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Interpolations.jl b/src/Interpolations.jl index f61affaa..192d9e22 100644 --- a/src/Interpolations.jl +++ b/src/Interpolations.jl @@ -219,8 +219,10 @@ onto the pre-allocated `Vx_d`, `Vy_d`, `Vz_d` 3D arrays located at the grid vert """ function velocity2vertex!(Vx_v, Vy_v, Vz_v, Vx, Vy, Vz) # infer size of grid - nx, ny, nz = size(Vx) - nv_x, nv_y, nv_z = nx - 1, ny - 2, nz - 2 + nx1, ny1, nz1 = size(Vx) + nx2, ny2, nz2 = size(Vy) + nx3, ny3, nz3 = size(Vz) + nv_x, nv_y, nv_z = max(nx1, nx2, nx3), max(ny1, ny2, ny3), max(nz1, nz2, nz3) # interpolate to cell vertices @parallel (@idx nv_x nv_y nv_z) _velocity2vertex!(Vx_v, Vy_v, Vz_v, Vx, Vy, Vz) @@ -228,11 +230,28 @@ function velocity2vertex!(Vx_v, Vy_v, Vz_v, Vx, Vy, Vz) end @parallel_indices (i, j, k) function _velocity2vertex!(Vx_v, Vy_v, Vz_v, Vx, Vy, Vz) - @inbounds begin + i1, j1, k1 = (i, j, k) .+ 1 + # if all((i, j1, k1) .≤ size(Vx_v)) + # Vx_v[i, j1, k1] = + # 0.25 * (Vx[i, j, k] + Vx[i, j + 1, k] + Vx[i, j, k + 1] + Vx[i, j + 1, k + 1]) + # end + # if all((i1, j, k1) .≤ size(Vy_v)) + # Vy_v[i1, j, k1] = + # 0.25 * (Vy[i, j, k] + Vy[i + 1, j, k] + Vy[i, j, k + 1] + Vy[i + 1, j, k + 1]) + # end + # if all((i1, j1, k) .≤ size(Vz_v)) + # Vz_v[i1, j1, k] = + # 0.25 * (Vz[i, j, k] + Vz[i, j + 1, k] + Vz[i + 1, j, k] + Vz[i + 1, j + 1, k]) + # end + if all((i, j, k) .≤ size(Vx_v)) Vx_v[i, j, k] = 0.25 * (Vx[i, j, k] + Vx[i, j + 1, k] + Vx[i, j, k + 1] + Vx[i, j + 1, k + 1]) + end + if all((i, j, k) .≤ size(Vy_v)) Vy_v[i, j, k] = 0.25 * (Vy[i, j, k] + Vy[i + 1, j, k] + Vy[i, j, k + 1] + Vy[i + 1, j, k + 1]) + end + if all((i, j, k) .≤ size(Vz_v)) Vz_v[i, j, k] = 0.25 * (Vz[i, j, k] + Vz[i, j + 1, k] + Vz[i + 1, j, k] + Vz[i + 1, j + 1, k]) end From bd77cd2ea51491e48d104f4f81fa60646e8e1d80 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Mon, 11 Mar 2024 18:01:19 +0100 Subject: [PATCH 04/60] fix small bugs in 3D flow BCs --- src/boundaryconditions/BoundaryConditions.jl | 163 +++++++++++++++---- 1 file changed, 130 insertions(+), 33 deletions(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index 91083f16..abf039e9 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -30,14 +30,12 @@ struct FlowBoundaryConditions{T,nD} <: AbstractBoundaryConditions end end -@inline bc_index(x::NTuple{2,T}) where {T} = mapreduce(xi -> max(size(xi)...), max, x) -@inline bc_index(x::T) where {T<:AbstractArray{<:Any,2}} = max(size(x)...) - -@inline function bc_index(x::NTuple{3,T}) where {T} - nx, ny, nz = size(x[1]) - return max((nx, ny), (ny, nz), (nx, nz)) +@inline bc_index(x::NTuple) = mapreduce(xi -> max(size(xi)...), max, x) +@inline function bc_index(x::NTuple{3, T}) where T + n = mapreduce(xi -> max(size(xi)...), max, x) + return n,n end - +@inline bc_index(x::T) where {T<:AbstractArray{<:Any,2}} = max(size(x)...) @inline function bc_index(x::T) where {T<:AbstractArray{<:Any,3}} nx, ny, nz = size(x) return max((nx, ny), (ny, nz), (nx, nz)) @@ -66,16 +64,17 @@ end Apply the prescribed flow boundary conditions `bc` on the `stokes` """ -function _flow_bcs!(bcs::FlowBoundaryConditions, V) +function _flow_bcs!(bcs::FlowBoundaryConditions, V::NTuple{N, T}) where {N, T} n = bc_index(V) - # no slip boundary conditions - do_bc(bcs.no_slip) && (@parallel (@idx n) no_slip!(V..., bcs.no_slip)) - # free slip boundary conditions - do_bc(bcs.free_slip) && (@parallel (@idx n) free_slip!(V..., bcs.free_slip)) - # periodic conditions - do_bc(bcs.periodicity) && - (@parallel (@idx n) periodic_boundaries!(V..., bcs.periodicity)) + for _ in 1:N-1 + do_bc(bcs.no_slip) && (@parallel (@idx n) no_slip!(V..., bcs.no_slip)) + # free slip boundary conditions + do_bc(bcs.free_slip) && (@parallel (@idx n) free_slip!(V..., bcs.free_slip)) + end + # # periodic conditions + # do_bc(bcs.periodicity) && + # (@parallel (@idx n) periodic_boundaries!(V..., bcs.periodicity)) return nothing end @@ -87,7 +86,7 @@ end # BOUNDARY CONDITIONS KERNELS -@parallel_indices (i) function no_slip!(Ax, Ay, bc) +@parallel_indices (i) function no_slip!(Ax::T, Ay::T, bc) where T @inbounds begin if bc.bot (i ≤ size(Ax, 2)) && (Ax[1, i] = 0.0) @@ -111,6 +110,46 @@ end return nothing end +@parallel_indices (i, j) function no_slip!(Ax::T, Ay::T, Az::T, bc) where T + @inbounds begin + if bc.bot + (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, 1] = -Ax[i, j, 2]) + (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 2)) && (Ay[i, j, 1] = -Ay[i, j, 2]) + (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, 1] = 0.0) + end + if bc.top + (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, end] = -Ax[i, j, end - 1]) + (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 2)) && (Ay[i, j, end] = -Ay[i, j, end - 1]) + (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, end] = 0.0) + end + if bc.left + (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[1, i, j] = 0.0) + (i ≤ size(Ay, 2)) && (j ≤ size(Ay, 3)) && (Ay[1, i, j] = -Ay[2, i, j]) + (i ≤ size(Az, 2)) && (j ≤ size(Az, 3)) && (Az[1, i, j] = -Az[2, i, j]) + end + if bc.right + (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[end, i, j] = 0.0) + (i ≤ size(Ay, 2)) && (j ≤ size(Ay, 3)) && (Ay[end, i, j] = -Ay[end-1, i, j]) + (i ≤ size(Az, 2)) && (j ≤ size(Az, 3)) && (Az[end, 1, j] = -Az[end-1, i, j]) + end + if bc.front + (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 3)) && (Ax[i, 1, j] = -Ax[i, 2, j]) + (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, 1, j] = 0.0) + (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[i, 1, j] = -Az[2, i, j]) + end + if bc.back + (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 3)) && (Ax[i, end, j] = -Ax[i, end - 1]) + (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, end, j] = 0.0) + (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[1, end, j] = -Az[end-1, i, j]) + end + + # # force corners to be zero + # bc.left && bc.bot && (Ax[1, 1, 1] = 0.0) + # bc.right && bc.top && (Ay[end, end, end] = 0.0) + end + return nothing +end + @parallel_indices (i) function free_slip!(Ax, Ay, bc) @inbounds begin if i ≤ size(Ax, 1) @@ -127,6 +166,24 @@ end @parallel_indices (i, j) function free_slip!(Ax, Ay, Az, bc) @inbounds begin + + # free slip in the top and bottom XY planes + if bc.top + if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) + Ax[i, j, 1] = Ax[i, j, 2] + end + if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) + Ay[i, j, 1] = Ay[i, j, 2] + end + end + if bc.bot + if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) + Ax[i, j, end] = Ax[i, j, end - 1] + end + if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) + Ay[i, j, end] = Ay[i, j, end - 1] + end + end # free slip in the front and back XZ planes if bc.front if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) @@ -144,23 +201,6 @@ end Az[i, end, j] = Az[i, end - 1, j] end end - # free slip in the front and back XY planes - if bc.top - if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) - Ax[i, j, 1] = Ax[i, j, 2] - end - if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) - Ay[i, j, 1] = Ay[i, j, 2] - end - end - if bc.bot - if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) - Ax[i, j, end] = Ax[i, j, end - 1] - end - if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) - Ay[i, j, end] = Ay[i, j, end - 1] - end - end # free slip in the front and back YZ planes if bc.left if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) @@ -182,6 +222,63 @@ end return nothing end +# @parallel_indices (i, j) function free_slip!(Ax, Ay, Az, bc) +# @inbounds begin +# # free slip in the front and back XZ planes +# if bc.front +# if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) +# Ax[i, 1, j] = Ax[i, 2, j] +# end +# if i ≤ size(Az, 1) && j ≤ size(Az, 3) +# Az[i, 1, j] = Az[i, 2, j] +# end +# end +# if bc.back +# if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) +# Ax[i, end, j] = Ax[i, end - 1, j] +# end +# if i ≤ size(Az, 1) && j ≤ size(Az, 3) +# Az[i, end, j] = Az[i, end - 1, j] +# end +# end +# # free slip in the front and back XY planes +# if bc.top +# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) +# Ax[i, j, 1] = Ax[i, j, 2] +# end +# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) +# Ay[i, j, 1] = Ay[i, j, 2] +# end +# end +# if bc.bot +# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) +# Ax[i, j, end] = Ax[i, j, end - 1] +# end +# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) +# Ay[i, j, end] = Ay[i, j, end - 1] +# end +# end +# # free slip in the front and back YZ planes +# if bc.left +# if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) +# Ay[1, i, j] = Ay[2, i, j] +# end +# if i ≤ size(Az, 2) && j ≤ size(Az, 3) +# Az[1, i, j] = Az[2, i, j] +# end +# end +# if bc.right +# if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) +# Ay[end, i, j] = Ay[end - 1, i, j] +# end +# if i ≤ size(Az, 2) && j ≤ size(Az, 3) +# Az[end, i, j] = Az[end - 1, i, j] +# end +# end +# end +# return nothing +# end + @parallel_indices (i) function free_slip!(T::_T, bc) where {_T<:AbstractArray{<:Any,2}} @inbounds begin if i ≤ size(T, 1) From 1f6c0f156bbd597bced1b1dccccba8333ddabd58 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Mon, 11 Mar 2024 18:01:39 +0100 Subject: [PATCH 05/60] WIP: 3D flow BCs unit tests --- test/test_boundary_conditions3D.jl | 189 +++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 test/test_boundary_conditions3D.jl diff --git a/test/test_boundary_conditions3D.jl b/test/test_boundary_conditions3D.jl new file mode 100644 index 00000000..2f4a229b --- /dev/null +++ b/test/test_boundary_conditions3D.jl @@ -0,0 +1,189 @@ +using Test, JustRelax, ParallelStencil +@init_parallel_stencil(Threads, Float64, 3) + +model = PS_Setup(:cpu, Float64, 3) +environment!(model) + +# @testset begin + n = 5 # number of elements + Vx, Vy, Vz = @rand(n + 1, n + 2, n + 2), @rand(n + 2, n + 1, n + 2), @rand(n + 2, n + 2, n + 1) + # free-slip + bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), + no_slip = (left = false, right = false, top = false, bot = false, front = false, back = false), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), + ) + flow_bcs!(bcs, Vx, Vy, Vz) + + # Vx + @test @views Vx[:, :, 1] == Vx[:, :, 2] # bottom + @test @views Vx[:, :, end] == Vx[:, :, end-1] # top + @test @views Vx[:, 1, :] == Vx[:, 2, :] # left + @test @views Vx[:, end, :] == Vx[:, end-1, :] # right + + # Vy + @test @views Vy[:, :, 1] == Vy[:, :, 2] # bottom + @test @views Vy[:, :, end] == Vy[:, :, end-1] # top + @test @views Vy[ 1, :, :] == Vy[ 2, :, :] # front + @test @views Vy[end, :, :] == Vy[end-1, :, :] # back + + # Vz + @test @views Vz[:, 1, :] == Vz[:, 2, :] # left + @test @views Vz[:, end, :] == Vz[:, end-1, :] # right + @test @views Vz[ 1, :, :] == Vz[ 2, :, :] # front + @test @views Vz[end, :, :] == Vz[end-1, :, :] # back + + # no-slip + bcs = FlowBoundaryConditions(; + free_slip = (left = false, right = false, top = false, bot = false, front = false, back = false), + no_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), + ) + flow_bcs!(bcs, Vx, Vy, Vz) + + # Test the ones that are zero + @test sum(!iszero(Vx[ 1, i, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # left + @test sum(!iszero(Vx[end, i, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # right + @test sum(!iszero(Vy[i, 1, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # front + @test sum(!iszero(Vy[i, end, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # back + @test sum(!iszero(Vz[i, j, 1]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # lefbottom + @test sum(!iszero(Vz[i, j, end]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # trop + + ## TODO: everything below here + @test sum(!iszero(Vx[end, i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test @views Vy[1, :] == -Vy[2, :] + @test @views Vy[end, :] == -Vy[end - 1, :] + @test @views Vx[:, 1] == -Vx[:, 2] + @test @views Vx[:, end] == -Vx[:, end - 1] + + # test with StokesArrays + ni = 5, 5 + stokes = StokesArrays(ni, ViscoElastic) + # free-slip + flow_bcs = FlowBoundaryConditions(; + no_slip=(left=false, right=false, top=false, bot=false), + free_slip=(left=true, right=true, top=true, bot=true), + periodicity=(left=false, right=false, top=false, bot=false), + ) + flow_bcs!(stokes, flow_bcs) + + @test @views stokes.V.Vx[:, 1] == stokes.V.Vx[:, 2] + @test @views stokes.V.Vx[:, end] == stokes.V.Vx[:, end - 1] + @test @views stokes.V.Vy[1, :] == stokes.V.Vy[2, :] + @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] + + # no-slip + flow_bcs = FlowBoundaryConditions(; + no_slip=(left=true, right=true, top=true, bot=true), + free_slip=(left=false, right=false, top=false, bot=false), + periodicity=(left=false, right=false, top=false, bot=false), + ) + flow_bcs!(stokes, flow_bcs) + + @test @views stokes.V.Vx[1, :] == + stokes.V.Vx[end, :] == + stokes.V.Vy[:, 1] == + stokes.V.Vy[:, end] + @test @views stokes.V.Vx[:, 2] == stokes.V.Vx[:, 3] / 3 + @test @views stokes.V.Vx[:, end-1] == stokes.V.Vx[:, end - 2] / 3 + @test @views stokes.V.Vy[2, :] == stokes.V.Vy[3, :] / 3 + @test @views stokes.V.Vy[end-1, :] == stokes.V.Vy[end - 2, :] / 3 +# end + + +# function free_slip!(Ax, Ay, Az, bc) +# n1, n2 = bc_index((Ax,Ay,Az)) + +# for i in 1:n1, j in 1:n2 +# # free slip in the top and bottom XY planes +# if bc.top +# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) +# Ax[i, j, 1] = Ax[i, j, 2] +# end +# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) +# Ay[i, j, 1] = Ay[i, j, 2] +# end +# end +# if bc.bot +# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) +# Ax[i, j, end] = Ax[i, j, end - 1] +# end +# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) +# Ay[i, j, end] = Ay[i, j, end - 1] +# end +# end +# end +# return nothing +# end + +V = Vx, Vy, Vz + + + +@parallel_indices (i, j) function foo!(Ax, Ay, Az, bc) + + # free slip in the top and bottom XY planes + if bc.top + if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) + Ax[i, j, 1] = Ax[i, j, 2] + end + if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) + Ay[i, j, 1] = Ay[i, j, 2] + end + end + if bc.bot + if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) + Ax[i, j, end] = Ax[i, j, end - 1] + end + if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) + Ay[i, j, end] = Ay[i, j, end - 1] + end + end + # # free slip in the front and back XZ planes + if bc.front + if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) + Ax[i, 1, j] = Ax[i, 2, j] + end + if i ≤ size(Az, 1) && j ≤ size(Az, 3) + Az[i, 1, j] = Az[i, 2, j] + end + end + # if bc.back + # if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) + # Ax[i, end, j] = Ax[i, end - 1, j] + # end + # if i ≤ size(Az, 1) && j ≤ size(Az, 3) + # Az[i, end, j] = Az[i, end - 1, j] + # end + # end + # # free slip in the front and back YZ planes + # if bc.left + # if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) + # Ay[1, i, j] = Ay[2, i, j] + # end + # if i ≤ size(Az, 2) && j ≤ size(Az, 3) + # Az[1, i, j] = Az[2, i, j] + # end + # end + # if bc.right + # if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) + # Ay[end, i, j] = Ay[end - 1, i, j] + # end + # if i ≤ size(Az, 2) && j ≤ size(Az, 3) + # Az[end, i, j] = Az[end - 1, i, j] + # end + # end + return nothing +end + +# free_slip!(Vx, Vy, Vz, bcs.free_slip) +n = 5 +V = Vx, Vy, Vz = @rand(n + 1, n + 2, n + 2), @rand(n + 2, n + 1, n + 2), @rand(n + 2, n + 2, n + 1) + +nn = bc_index(V) +@parallel (@idx nn) foo!(Vx, Vy, Vz, bcs.free_slip) + +Vx[:, :, 1] .== Vx[:, :, 2] +Vx[:, :, end] .== Vx[:, :, end-1] \ No newline at end of file From 02f608e37e45d652c47a2878d2dc9749140edbc2 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Mon, 11 Mar 2024 18:01:52 +0100 Subject: [PATCH 06/60] improve 3D subduction setup scripts --- subduction/Subduction3D.jl | 40 ++++++++++++-------- subduction/Subduction_rheology.jl | 62 +++++++++++++++---------------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/subduction/Subduction3D.jl b/subduction/Subduction3D.jl index 21543684..d0bda3f6 100644 --- a/subduction/Subduction3D.jl +++ b/subduction/Subduction3D.jl @@ -2,7 +2,7 @@ using CUDA using JustRelax, JustRelax.DataIO import JustRelax.@cell using ParallelStencil -@init_parallel_stencil(Threads, Float64, 3) +@init_parallel_stencil(CUDA, Float64, 3) using JustPIC using JustPIC._3D @@ -72,7 +72,8 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) particle_args = (pPhases, ) # Assign particles phases anomaly - init_phases!(pPhases, PTArray(phases_GMG), particles, xvi) + phases_device = PTArray(phases_GMG) + init_phases!(pPhases, phases_device, particles, xvi) phase_ratios = PhaseRatio(ni, length(rheology)) @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) # ---------------------------------------------------- @@ -80,7 +81,7 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) # STOKES --------------------------------------------- # Allocate arrays needed for every Stokes problem stokes = StokesArrays(ni, ViscoElastic) - pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.5 / √3.1) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-3, CFL = 0.95 / √3.1) # ---------------------------------------------------- # TEMPERATURE PROFILE -------------------------------- @@ -109,8 +110,8 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) # Boundary conditions flow_bcs = FlowBoundaryConditions(; - free_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), - no_slip = (left = false, right = false, top = false, bot = false, front = false, back = false), + free_slip = (left = true , right = true , top = true , bot = false , front = true , back = true ), + no_slip = (left = false, right = false, top = false, bot = true, front = false, back = false), periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), ) flow_bcs!(stokes, flow_bcs) # apply boundary conditions @@ -152,7 +153,8 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) end # Time loop t, it = 0.0, 0 - while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + while it < 150 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs # # interpolate fields from particle to grid vertices # particle2grid!(thermal.T, pT, xvi, particles) @@ -184,7 +186,7 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) viscosity_cutoff = (1e18, 1e24) ); @parallel (JustRelax.@idx ni) JustRelax.Stokes3D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) - dt = compute_dt(stokes, di, dt_diff) / 2 + dt = compute_dt(stokes, di) # ------------------------------ # # Thermal solver --------------- @@ -226,7 +228,7 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) t += dt # Data I/O and plotting --------------------- - if it == 1 || rem(it, 1) == 0 + if it == 1 || rem(it, 5) == 0 checkpointing(figdir, stokes, thermal.T, η, t) if do_vtk @@ -237,32 +239,38 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) εxy = Array(stokes.ε.xy), Vx = Array(Vx_v), Vy = Array(Vy_v), + Vz = Array(Vz_v), ) data_c = (; - Tc = Array(thermal.Tc), P = Array(stokes.P), τxx = Array(stokes.τ.xx), τyy = Array(stokes.τ.yy), εxx = Array(stokes.ε.xx), εyy = Array(stokes.ε.yy), - η = Array(log10.(η)), + η = Array(η), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + Array(Vz_v), ) save_vtk( joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), xvi, xci, data_v, - data_c + data_c, + velocity_v ) end slice_j = ny >>> 1 # Make Makie figure fig = Figure(size = (1400, 1800), title = "t = $t") - ax1 = Axis(fig[1,1], aspect = ar, title = "P [GPA] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") - ax2 = Axis(fig[2,1], aspect = ar, title = "τII [MPa]") - ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") - ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + ax1 = Axis(fig[1,1], title = "P [GPA] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], title = "τII [MPa]") + ax3 = Axis(fig[1,3], title = "log10(εII)") + ax4 = Axis(fig[2,3], title = "log10(η)") # Plot temperature h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[3].*1e-3, Array(stokes.P[:, slice_j, :]./1e9) , colormap=:lajolla) # Plot particles phase @@ -301,4 +309,4 @@ end # (Path)/folder where output data and figures are stored figdir = "Subduction3D" -# main3D(igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); \ No newline at end of file +main3D(igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); diff --git a/subduction/Subduction_rheology.jl b/subduction/Subduction_rheology.jl index 27435edc..d82c28f4 100644 --- a/subduction/Subduction_rheology.jl +++ b/subduction/Subduction_rheology.jl @@ -32,46 +32,44 @@ end function init_phases!(phases, phase_grid, particles, xvi) ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end - @parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} - ni = size(phases) + ni = size(phases) - for ip in JustRelax.cellaxes(phases) - # quick escape - iszero(JustRelax.@cell(index[ip, I...])) && continue + for ip in JustRelax.cellaxes(phases) + # quick escape + JustRelax.@cell(index[ip, I...]) == 0 && continue - pᵢ = ntuple(Val(N)) do i - JustRelax.@cell pcoords[i][ip, I...] - end + pᵢ = ntuple(Val(N)) do i + JustRelax.@cell pcoords[i][ip, I...] + end - d = Inf # distance to the nearest particle - particle_phase = -1 - for offi in 0:1, offj in 0:1, offk in 0:1 - ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1, offk in 0:1 + ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk - !(ii ≤ ni[1]) && continue - !(jj ≤ ni[2]) && continue - !(kk ≤ ni[3]) && continue + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + !(kk ≤ ni[3]) && continue - xvᵢ = ( - xvi[1][ii], - xvi[2][jj], - xvi[3][kk], - ) - # @show xvᵢ ii jj kk - d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) - if d_ijk < d - d = d_ijk - particle_phase = phase_grid[ii, jj, kk] - end + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + xvi[3][kk], + ) + # @show xvᵢ ii jj kk + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj, kk] end - JustRelax.@cell phases[ip, I...] = particle_phase + 1.0 - end - return nothing + JustRelax.@cell phases[ip, I...] = particle_phase + 1.0 end - @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) -end - + return nothing +end \ No newline at end of file From e4cc0afbfcfbf0702ceeced32b44d0d9962cd00c Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Mon, 11 Mar 2024 22:39:59 +0100 Subject: [PATCH 07/60] finish BCs unit testing --- test/test_boundary_conditions2D.jl | 122 +++++++--------- test/test_boundary_conditions3D.jl | 226 ++++++++++++----------------- 2 files changed, 151 insertions(+), 197 deletions(-) diff --git a/test/test_boundary_conditions2D.jl b/test/test_boundary_conditions2D.jl index a301afce..f3594ded 100644 --- a/test/test_boundary_conditions2D.jl +++ b/test/test_boundary_conditions2D.jl @@ -6,94 +6,84 @@ using ParallelStencil model = PS_Setup(:cpu, Float64, 2) environment!(model) -@testset begin +@testset "Flow boundary conditions 2D" begin # periodicity - n = 5 # number of elements + n = 5 # number of elements Vx, Vy = @rand(n + 1, n + 2), @rand(n + 2, n + 1) - bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=true, right=true, top=true, bot=true), - ) - flow_bcs!(bcs, Vx, Vy) - - @test @views Vx[:, 1] == Vx[:, end - 1] - @test @views Vx[:, end] == Vx[:, 2] - @test @views Vy[1, :] == Vy[end - 1, :] - @test @views Vy[end, :] == Vy[2, :] - - # free-slip - bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=true, right=true, top=true, bot=true), - periodicity=(left=false, right=false, top=false, bot=false), + bcs = FlowBoundaryConditions(; + no_slip = (left = false, right = false, top = false, bot = false), + free_slip = (left = true, right = true, top = true, bot = true), + periodicity = (left = false, right = false, top = false, bot = false), ) flow_bcs!(bcs, Vx, Vy) - @test @views Vx[:, 1] == Vx[:, 2] - @test @views Vx[:, end] == Vx[:, end - 1] - @test @views Vy[1, :] == Vy[2, :] - @test @views Vy[end, :] == Vy[end - 1, :] + @test @views Vx[ :, 1] == Vx[ :, 2] + @test @views Vx[ :, end] == Vx[ :, end - 1] + @test @views Vy[ 1, :] == Vy[ 2, :] + @test @views Vy[end, :] == Vy[end - 1, :] # no-slip bcs = FlowBoundaryConditions(; - no_slip=(left=true, right=true, top=true, bot=true), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=false, right=false, top=false, bot=false), + no_slip = (left = true, right = true, top = true, bot = true), + free_slip = (left = false, right = false, top = false, bot = false), + periodicity = (left = false, right = false, top = false, bot = false), ) flow_bcs!(bcs, Vx, Vy) + @test sum(!iszero(Vx[1 , i]) for i in axes(Vx,2)) == 0 @test sum(!iszero(Vx[end, i]) for i in axes(Vx,2)) == 0 @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test @views Vy[1, :] == -Vy[2, :] - @test @views Vy[end, :] == -Vy[end - 1, :] - @test @views Vx[:, 1] == -Vx[:, 2] - @test @views Vx[:, end] == -Vx[:, end - 1] + @test @views Vy[ 1, :] == -Vy[ 2, :] + @test @views Vy[end, :] == -Vy[end - 1, :] + @test @views Vx[ :, 1] == -Vx[: , 2] + @test @views Vx[ :, end] == -Vx[: , end - 1] # test with StokesArrays - # periodicity - ni = 5, 5 + ni = n, n stokes = StokesArrays(ni, ViscoElastic) - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=true, right=true, top=true, bot=true), + # free-slip + bcs = FlowBoundaryConditions(; + no_slip = (left = false, right = false, top = false, bot = false), + free_slip = (left = true, right = true, top = true, bot = true), + periodicity = (left = false, right = false, top = false, bot = false), ) - flow_bcs!(stokes, flow_bcs) + flow_bcs!(stokes, bcs) - @test @views stokes.V.Vx[:, 1] == stokes.V.Vx[:, end - 1] - @test @views stokes.V.Vx[:, end] == stokes.V.Vx[:, 2] - @test @views stokes.V.Vy[1, :] == stokes.V.Vy[end - 1, :] - @test @views stokes.V.Vy[end, :] == stokes.V.Vy[2, :] + @test @views stokes.V.Vx[ :, 1] == stokes.V.Vx[ :, 2] + @test @views stokes.V.Vx[ :, end] == stokes.V.Vx[ :, end - 1] + @test @views stokes.V.Vy[ 1, :] == stokes.V.Vy[ 2, :] + @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] - # free-slip - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=true, right=true, top=true, bot=true), - periodicity=(left=false, right=false, top=false, bot=false), + # no-slip + bcs = FlowBoundaryConditions(; + no_slip = (left = true, right = true, top = true, bot = true), + free_slip = (left = false, right = false, top = false, bot = false), + periodicity = (left = false, right = false, top = false, bot = false), ) - flow_bcs!(stokes, flow_bcs) + flow_bcs!(stokes, bcs) + @test sum(!iszero(stokes.V.Vx[1 , i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(stokes.V.Vx[end, i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test @views stokes.V.Vy[ 1, :] == -stokes.V.Vy[ 2, :] + @test @views stokes.V.Vy[end, :] == -stokes.V.Vy[end - 1, :] + @test @views stokes.V.Vx[ :, 1] == -stokes.V.Vx[: , 2] + @test @views stokes.V.Vx[ :, end] == -stokes.V.Vx[: , end - 1] +end - @test @views stokes.V.Vx[:, 1] == stokes.V.Vx[:, 2] - @test @views stokes.V.Vx[:, end] == stokes.V.Vx[:, end - 1] - @test @views stokes.V.Vy[1, :] == stokes.V.Vy[2, :] - @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] - # no-slip - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=true, right=true, top=true, bot=true), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=false, right=false, top=false, bot=false), +@testset "Temperature boundary conditions 2D" begin + ni = 5, 5 # number of elements + thermal = ThermalArrays(ni) + # free-slip + bcs = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = true, bot = true), ) - flow_bcs!(stokes, flow_bcs) + thermal_bcs!(thermal, bcs) - @test @views stokes.V.Vx[1, :] == - stokes.V.Vx[end, :] == - stokes.V.Vy[:, 1] == - stokes.V.Vy[:, end] - @test @views stokes.V.Vx[:, 2] == stokes.V.Vx[:, 3] / 3 - @test @views stokes.V.Vx[:, end-1] == stokes.V.Vx[:, end - 2] / 3 - @test @views stokes.V.Vy[2, :] == stokes.V.Vy[3, :] / 3 - @test @views stokes.V.Vy[end-1, :] == stokes.V.Vy[end - 2, :] / 3 -end + @test @views thermal.T[ :, 1] == thermal.T[ :, 2] + @test @views thermal.T[ :, end] == thermal.T[ :, end - 1] + @test @views thermal.T[ 1, :] == thermal.T[ 2, :] + @test @views thermal.T[end, :] == thermal.T[end - 1, :] +end \ No newline at end of file diff --git a/test/test_boundary_conditions3D.jl b/test/test_boundary_conditions3D.jl index 2f4a229b..9353b37e 100644 --- a/test/test_boundary_conditions3D.jl +++ b/test/test_boundary_conditions3D.jl @@ -4,7 +4,7 @@ using Test, JustRelax, ParallelStencil model = PS_Setup(:cpu, Float64, 3) environment!(model) -# @testset begin +@testset "Flow boundary conditions 3D" begin n = 5 # number of elements Vx, Vy, Vz = @rand(n + 1, n + 2, n + 2), @rand(n + 2, n + 1, n + 2), @rand(n + 2, n + 2, n + 1) # free-slip @@ -20,13 +20,11 @@ environment!(model) @test @views Vx[:, :, end] == Vx[:, :, end-1] # top @test @views Vx[:, 1, :] == Vx[:, 2, :] # left @test @views Vx[:, end, :] == Vx[:, end-1, :] # right - # Vy @test @views Vy[:, :, 1] == Vy[:, :, 2] # bottom @test @views Vy[:, :, end] == Vy[:, :, end-1] # top @test @views Vy[ 1, :, :] == Vy[ 2, :, :] # front @test @views Vy[end, :, :] == Vy[end-1, :, :] # back - # Vz @test @views Vz[:, 1, :] == Vz[:, 2, :] # left @test @views Vz[:, end, :] == Vz[:, end-1, :] # right @@ -39,151 +37,117 @@ environment!(model) no_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), ) + Vx, Vy, Vz = @rand(n + 1, n + 2, n + 2), @rand(n + 2, n + 1, n + 2), @rand(n + 2, n + 2, n + 1) flow_bcs!(bcs, Vx, Vy, Vz) # Test the ones that are zero @test sum(!iszero(Vx[ 1, i, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # left @test sum(!iszero(Vx[end, i, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # right - @test sum(!iszero(Vy[i, 1, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # front - @test sum(!iszero(Vy[i, end, j]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # back - @test sum(!iszero(Vz[i, j, 1]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # lefbottom - @test sum(!iszero(Vz[i, j, end]) for i in axes(Vx, 2), j in axes(Vx, 3)) == 0 # trop + @test sum(!iszero(Vy[i, 1, j]) for i in axes(Vy, 1), j in axes(Vy, 3)) == 0 # front + @test sum(!iszero(Vy[i, end, j]) for i in axes(Vy, 1), j in axes(Vy, 3)) == 0 # back + @test sum(!iszero(Vz[i, j, 1]) for i in axes(Vz, 1), j in axes(Vz, 2)) == 0 # bottom + @test sum(!iszero(Vz[i, j, end]) for i in axes(Vz, 1), j in axes(Vz, 2)) == 0 # top - ## TODO: everything below here - @test sum(!iszero(Vx[end, i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test @views Vy[1, :] == -Vy[2, :] - @test @views Vy[end, :] == -Vy[end - 1, :] - @test @views Vx[:, 1] == -Vx[:, 2] - @test @views Vx[:, end] == -Vx[:, end - 1] + # Vx + @test @views Vx[:, :, 1] == -Vx[:, :, 2] # bottom + @test @views Vx[:, :, end] == -Vx[:, :, end-1] # top + @test @views Vx[:, 1, :] == -Vx[:, 2, :] # left + @test @views Vx[:, end, :] == -Vx[:, end-1, :] # right + # Vy + @test @views Vy[ :, :, 1] == -Vy[ :, :, 2] # bottom + @test @views Vy[ :, :, end] == -Vy[ :, :, end-1] # top + @test @views Vy[ 1, :, :] == -Vy[ 2, :, :] # front + @test @views Vy[end, :, :] == -Vy[end-1, :, :] # back + # Vz + @test @views Vz[ :, 1, :] == -Vz[ :, 2, :] # left + @test @views Vz[ :, end, :] == -Vz[ :, end-1, :] # right + @test @views Vz[ 1, :, :] == -Vz[ 2, :, :] # front + @test @views Vz[end, :, :] == -Vz[end-1, :, :] # back # test with StokesArrays - ni = 5, 5 + ni = 5, 5, 5 stokes = StokesArrays(ni, ViscoElastic) # free-slip - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=true, right=true, top=true, bot=true), - periodicity=(left=false, right=false, top=false, bot=false), + bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), + no_slip = (left = false, right = false, top = false, bot = false, front = false, back = false), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), ) - flow_bcs!(stokes, flow_bcs) + flow_bcs!(stokes, bcs) - @test @views stokes.V.Vx[:, 1] == stokes.V.Vx[:, 2] - @test @views stokes.V.Vx[:, end] == stokes.V.Vx[:, end - 1] - @test @views stokes.V.Vy[1, :] == stokes.V.Vy[2, :] - @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] + # Vx + @test @views stokes.V.Vx[:, :, 1] == stokes.V.Vx[:, :, 2] # bottom + @test @views stokes.V.Vx[:, :, end] == stokes.V.Vx[:, :, end-1] # top + @test @views stokes.V.Vx[:, 1, :] == stokes.V.Vx[:, 2, :] # left + @test @views stokes.V.Vx[:, end, :] == stokes.V.Vx[:, end-1, :] # right + # Vy + @test @views stokes.V.Vy[:, :, 1] ==stokes.V. Vy[:, :, 2] # bottom + @test @views stokes.V.Vy[:, :, end] ==stokes.V. Vy[:, :, end-1] # top + @test @views stokes.V.Vy[ 1, :, :] ==stokes.V. Vy[ 2, :, :] # front + @test @views stokes.V.Vy[end, :, :] ==stokes.V. Vy[end-1, :, :] # back + # Vz + @test @views stokes.V.Vz[:, 1, :] == stokes.V.Vz[:, 2, :] # left + @test @views stokes.V.Vz[:, end, :] == stokes.V.Vz[:, end-1, :] # right + @test @views stokes.V.Vz[ 1, :, :] == stokes.V.Vz[ 2, :, :] # front + @test @views stokes.V.Vz[end, :, :] == stokes.V.Vz[end-1, :, :] # back # no-slip - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=true, right=true, top=true, bot=true), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=false, right=false, top=false, bot=false), + bcs = FlowBoundaryConditions(; + free_slip = (left = false, right = false, top = false, bot = false, front = false, back = false), + no_slip = (left = true , right = true , top = true , bot = true , front = true , back = true ), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), ) - flow_bcs!(stokes, flow_bcs) - - @test @views stokes.V.Vx[1, :] == - stokes.V.Vx[end, :] == - stokes.V.Vy[:, 1] == - stokes.V.Vy[:, end] - @test @views stokes.V.Vx[:, 2] == stokes.V.Vx[:, 3] / 3 - @test @views stokes.V.Vx[:, end-1] == stokes.V.Vx[:, end - 2] / 3 - @test @views stokes.V.Vy[2, :] == stokes.V.Vy[3, :] / 3 - @test @views stokes.V.Vy[end-1, :] == stokes.V.Vy[end - 2, :] / 3 -# end - - -# function free_slip!(Ax, Ay, Az, bc) -# n1, n2 = bc_index((Ax,Ay,Az)) - -# for i in 1:n1, j in 1:n2 -# # free slip in the top and bottom XY planes -# if bc.top -# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) -# Ax[i, j, 1] = Ax[i, j, 2] -# end -# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) -# Ay[i, j, 1] = Ay[i, j, 2] -# end -# end -# if bc.bot -# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) -# Ax[i, j, end] = Ax[i, j, end - 1] -# end -# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) -# Ay[i, j, end] = Ay[i, j, end - 1] -# end -# end -# end -# return nothing -# end - -V = Vx, Vy, Vz - + flow_bcs!(stokes, bcs) + + # Test the ones that are zero + @test sum(!iszero(stokes.V.Vx[ 1, i, j]) for i in axes(stokes.V.Vx, 2), j in axes(stokes.V.Vx, 3)) == 0 # left + @test sum(!iszero(stokes.V.Vx[end, i, j]) for i in axes(stokes.V.Vx, 2), j in axes(stokes.V.Vx, 3)) == 0 # right + @test sum(!iszero(stokes.V.Vy[i, 1, j]) for i in axes(stokes.V.Vx, 2), j in axes(stokes.V.Vx, 3)) == 0 # front + @test sum(!iszero(stokes.V.Vy[i, end, j]) for i in axes(stokes.V.Vx, 2), j in axes(stokes.V.Vx, 3)) == 0 # back + @test sum(!iszero(stokes.V.Vz[i, j, 1]) for i in axes(stokes.V.Vx, 2), j in axes(stokes.V.Vx, 3)) == 0 # bottom + @test sum(!iszero(stokes.V.Vz[i, j, end]) for i in axes(stokes.V.Vx, 2), j in axes(stokes.V.Vx, 3)) == 0 # top + # Vx + @test @views stokes.V.Vx[:, :, 1] == -stokes.V.Vx[:, :, 2] # bottom + @test @views stokes.V.Vx[:, :, end] == -stokes.V.Vx[:, :, end-1] # top + @test @views stokes.V.Vx[:, 1, :] == -stokes.V.Vx[:, 2, :] # left + @test @views stokes.V.Vx[:, end, :] == -stokes.V.Vx[:, end-1, :] # right + # Vy + @test @views stokes.V.Vy[ :, :, 1] == -stokes.V.Vy[ :, :, 2] # bottom + @test @views stokes.V.Vy[ :, :, end] == -stokes.V.Vy[ :, :, end-1] # top + @test @views stokes.V.Vy[ 1, :, :] == -stokes.V.Vy[ 2, :, :] # front + @test @views stokes.V.Vy[end, :, :] == -stokes.V.Vy[end-1, :, :] # back + # Vz + @test @views stokes.V.Vz[ :, 1, :] == -stokes.V.Vz[ :, 2, :] # left + @test @views stokes.V.Vz[ :, end, :] == -stokes.V.Vz[ :, end-1, :] # right + @test @views stokes.V.Vz[ 1, :, :] == -stokes.V.Vz[ 2, :, :] # front + @test @views stokes.V.Vz[end, :, :] == -stokes.V.Vz[end-1, :, :] # back -@parallel_indices (i, j) function foo!(Ax, Ay, Az, bc) - - # free slip in the top and bottom XY planes - if bc.top - if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) - Ax[i, j, 1] = Ax[i, j, 2] - end - if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) - Ay[i, j, 1] = Ay[i, j, 2] - end - end - if bc.bot - if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) - Ax[i, j, end] = Ax[i, j, end - 1] - end - if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) - Ay[i, j, end] = Ay[i, j, end - 1] - end - end - # # free slip in the front and back XZ planes - if bc.front - if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) - Ax[i, 1, j] = Ax[i, 2, j] - end - if i ≤ size(Az, 1) && j ≤ size(Az, 3) - Az[i, 1, j] = Az[i, 2, j] - end - end - # if bc.back - # if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) - # Ax[i, end, j] = Ax[i, end - 1, j] - # end - # if i ≤ size(Az, 1) && j ≤ size(Az, 3) - # Az[i, end, j] = Az[i, end - 1, j] - # end - # end - # # free slip in the front and back YZ planes - # if bc.left - # if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) - # Ay[1, i, j] = Ay[2, i, j] - # end - # if i ≤ size(Az, 2) && j ≤ size(Az, 3) - # Az[1, i, j] = Az[2, i, j] - # end - # end - # if bc.right - # if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) - # Ay[end, i, j] = Ay[end - 1, i, j] - # end - # if i ≤ size(Az, 2) && j ≤ size(Az, 3) - # Az[end, i, j] = Az[end - 1, i, j] - # end - # end - return nothing end -# free_slip!(Vx, Vy, Vz, bcs.free_slip) -n = 5 -V = Vx, Vy, Vz = @rand(n + 1, n + 2, n + 2), @rand(n + 2, n + 1, n + 2), @rand(n + 2, n + 2, n + 1) - -nn = bc_index(V) -@parallel (@idx nn) foo!(Vx, Vy, Vz, bcs.free_slip) +@testset "Temperature boundary conditions 3D" begin + ni = 5, 5, 5 # number of elements + thermal = ThermalArrays(ni) + # free-slip + bcs = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = true, bot = true, front = true, back = true), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), + ) + thermal_bcs!(thermal, bcs) -Vx[:, :, 1] .== Vx[:, :, 2] -Vx[:, :, end] .== Vx[:, :, end-1] \ No newline at end of file + # Vx + @test @views thermal.T[:, :, 1] == thermal.T[:, :, 2] # bottom + @test @views thermal.T[:, :, end] == thermal.T[:, :, end-1] # top + @test @views thermal.T[:, 1, :] == thermal.T[:, 2, :] # left + @test @views thermal.T[:, end, :] == thermal.T[:, end-1, :] # right + # Vy + @test @views thermal.T[:, :, 1] == thermal.T[:, :, 2] # bottom + @test @views thermal.T[:, :, end] == thermal.T[:, :, end-1] # top + @test @views thermal.T[ 1, :, :] == thermal.T[ 2, :, :] # front + @test @views thermal.T[end, :, :] == thermal.T[end-1, :, :] # back + # Vz + @test @views thermal.T[:, 1, :] == thermal.T[:, 2, :] # left + @test @views thermal.T[:, end, :] == thermal.T[:, end-1, :] # right + @test @views thermal.T[ 1, :, :] == thermal.T[ 2, :, :] # front + @test @views thermal.T[end, :, :] == thermal.T[end-1, :, :] # back +end From f05704198b402d831ea3ee97d7598021ea901b09 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Mon, 11 Mar 2024 22:40:57 +0100 Subject: [PATCH 08/60] improve BCs --- src/boundaryconditions/BoundaryConditions.jl | 102 ++++--------------- 1 file changed, 21 insertions(+), 81 deletions(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index abf039e9..4cf2ba4e 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -48,17 +48,19 @@ end Apply the prescribed heat boundary conditions `bc` on the `T` """ -function thermal_bcs!(T, bcs::TemperatureBoundaryConditions) +@inline function thermal_bcs!(T::AbstractArray{_T, N}, bcs::TemperatureBoundaryConditions) where {_T, N} n = bc_index(T) - # no flux boundary conditions - do_bc(bcs.no_flux) && (@parallel (@idx n) free_slip!(T, bcs.no_flux)) - # periodic conditions - do_bc(bcs.periodicity) && (@parallel (@idx n) periodic_boundaries!(T, bcs.periodicity)) + for _ in 1:N-1 + # no flux boundary conditions + do_bc(bcs.no_flux) && (@parallel (@idx n) free_slip!(T, bcs.no_flux)) + end return nothing end +@inline thermal_bcs!(thermal::ThermalArrays, bcs::TemperatureBoundaryConditions) = thermal_bcs!(thermal.T, bcs) + """ flow_bcs!(stokes, bcs::FlowBoundaryConditions, di) @@ -66,15 +68,12 @@ Apply the prescribed flow boundary conditions `bc` on the `stokes` """ function _flow_bcs!(bcs::FlowBoundaryConditions, V::NTuple{N, T}) where {N, T} n = bc_index(V) - # no slip boundary conditions - for _ in 1:N-1 + for _ in 1:2 + # no slip boundary conditions do_bc(bcs.no_slip) && (@parallel (@idx n) no_slip!(V..., bcs.no_slip)) # free slip boundary conditions do_bc(bcs.free_slip) && (@parallel (@idx n) free_slip!(V..., bcs.free_slip)) end - # # periodic conditions - # do_bc(bcs.periodicity) && - # (@parallel (@idx n) periodic_boundaries!(V..., bcs.periodicity)) return nothing end @@ -115,37 +114,35 @@ end if bc.bot (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, 1] = -Ax[i, j, 2]) (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 2)) && (Ay[i, j, 1] = -Ay[i, j, 2]) - (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, 1] = 0.0) end if bc.top (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, end] = -Ax[i, j, end - 1]) (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 2)) && (Ay[i, j, end] = -Ay[i, j, end - 1]) - (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, end] = 0.0) end if bc.left - (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[1, i, j] = 0.0) (i ≤ size(Ay, 2)) && (j ≤ size(Ay, 3)) && (Ay[1, i, j] = -Ay[2, i, j]) (i ≤ size(Az, 2)) && (j ≤ size(Az, 3)) && (Az[1, i, j] = -Az[2, i, j]) end if bc.right - (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[end, i, j] = 0.0) - (i ≤ size(Ay, 2)) && (j ≤ size(Ay, 3)) && (Ay[end, i, j] = -Ay[end-1, i, j]) - (i ≤ size(Az, 2)) && (j ≤ size(Az, 3)) && (Az[end, 1, j] = -Az[end-1, i, j]) + (i ≤ size(Ay, 2)) && (j ≤ size(Ay, 3)) && (Ay[end, i, j] = -Ay[end - 1, i, j]) + (i ≤ size(Az, 2)) && (j ≤ size(Az, 3)) && (Az[end, i, j] = -Az[end - 1, i, j]) end if bc.front (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 3)) && (Ax[i, 1, j] = -Ax[i, 2, j]) - (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, 1, j] = 0.0) - (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[i, 1, j] = -Az[2, i, j]) + (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[i, 1, j] = -Az[i, 2, j]) end if bc.back - (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 3)) && (Ax[i, end, j] = -Ax[i, end - 1]) - (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, end, j] = 0.0) - (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[1, end, j] = -Az[end-1, i, j]) + (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 3)) && (Ax[i, end, j] = -Ax[i, end - 1, j]) + (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[i, end, j] = -Az[i, end - 1, j]) end - # # force corners to be zero - # bc.left && bc.bot && (Ax[1, 1, 1] = 0.0) - # bc.right && bc.top && (Ay[end, end, end] = 0.0) + bc.bot && (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, 1] = 0.0) + bc.top && (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, end] = 0.0) + bc.left && (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[1, i, j] = 0.0) + bc.right && (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[end, i, j] = 0.0) + bc.front && (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, 1, j] = 0.0) + bc.back && (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, end, j] = 0.0) + end return nothing end @@ -222,63 +219,6 @@ end return nothing end -# @parallel_indices (i, j) function free_slip!(Ax, Ay, Az, bc) -# @inbounds begin -# # free slip in the front and back XZ planes -# if bc.front -# if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) -# Ax[i, 1, j] = Ax[i, 2, j] -# end -# if i ≤ size(Az, 1) && j ≤ size(Az, 3) -# Az[i, 1, j] = Az[i, 2, j] -# end -# end -# if bc.back -# if i ≤ size(Ax, 1) && j ≤ size(Ax, 3) -# Ax[i, end, j] = Ax[i, end - 1, j] -# end -# if i ≤ size(Az, 1) && j ≤ size(Az, 3) -# Az[i, end, j] = Az[i, end - 1, j] -# end -# end -# # free slip in the front and back XY planes -# if bc.top -# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) -# Ax[i, j, 1] = Ax[i, j, 2] -# end -# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) -# Ay[i, j, 1] = Ay[i, j, 2] -# end -# end -# if bc.bot -# if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) -# Ax[i, j, end] = Ax[i, j, end - 1] -# end -# if i ≤ size(Ay, 1) && j ≤ size(Ay, 2) -# Ay[i, j, end] = Ay[i, j, end - 1] -# end -# end -# # free slip in the front and back YZ planes -# if bc.left -# if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) -# Ay[1, i, j] = Ay[2, i, j] -# end -# if i ≤ size(Az, 2) && j ≤ size(Az, 3) -# Az[1, i, j] = Az[2, i, j] -# end -# end -# if bc.right -# if i ≤ size(Ay, 2) && j ≤ size(Ay, 3) -# Ay[end, i, j] = Ay[end - 1, i, j] -# end -# if i ≤ size(Az, 2) && j ≤ size(Az, 3) -# Az[end, i, j] = Az[end - 1, i, j] -# end -# end -# end -# return nothing -# end - @parallel_indices (i) function free_slip!(T::_T, bc) where {_T<:AbstractArray{<:Any,2}} @inbounds begin if i ≤ size(T, 1) From 4e3048800bd23fc76b154622e9f49875d527204d Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Tue, 12 Mar 2024 11:43:11 +0100 Subject: [PATCH 09/60] add some wrapper methods without `@parallel` --- src/MetaJustRelax.jl | 2 +- src/phases/phases.jl | 9 ++++++++- src/rheology/BuoyancyForces.jl | 17 +++++++++++++++++ src/rheology/Viscosity.jl | 27 +++++++++++++++++++++++++++ src/stokes/StressKernels.jl | 2 +- 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/MetaJustRelax.jl b/src/MetaJustRelax.jl index b71e7fd9..c5d7131f 100644 --- a/src/MetaJustRelax.jl +++ b/src/MetaJustRelax.jl @@ -116,7 +116,7 @@ function environment!(model::PS_Setup{T,N}) where {T,N} apply_free_slip! include(joinpath(@__DIR__, "phases/phases.jl")) - export PhaseRatio, fn_ratio, phase_ratios_center + export PhaseRatio, fn_ratio, phase_ratios_center, phase_ratios_center! include(joinpath(@__DIR__, "rheology/BuoyancyForces.jl")) export compute_ρg! diff --git a/src/phases/phases.jl b/src/phases/phases.jl index 9a7890a5..72669c21 100644 --- a/src/phases/phases.jl +++ b/src/phases/phases.jl @@ -119,7 +119,14 @@ end end # ParallelStencil launch kernel for 2D -@parallel_indices (I...) function phase_ratios_center( + +function phase_ratios_center!(phase_ratios::PhaseRatio, particles, xci, di, pPhases) + ni = size(pPhases) + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + return nothing +end + +@parallel_indices (I...) function phase_ratios_center_kernel( ratio_centers, pxi::NTuple{N,T1}, xci::NTuple{N,T2}, di::NTuple{N,T3}, phases ) where {N,T1,T2,T3} diff --git a/src/rheology/BuoyancyForces.jl b/src/rheology/BuoyancyForces.jl index 59a84548..ab800808 100644 --- a/src/rheology/BuoyancyForces.jl +++ b/src/rheology/BuoyancyForces.jl @@ -3,6 +3,12 @@ Calculate the buoyance forces `ρg` for the given GeoParams.jl `rheology` object and correspondent arguments `args`. """ +function compute_ρg!(ρg, rheology, args) # index arguments for the current cell cell center + ni = size(ρg) + @parallel (@idx ni) compute_ρg!(ρg, rheology, args) + return nothing +end + @parallel_indices (I...) function compute_ρg!(ρg, rheology, args) # index arguments for the current cell cell center args_ijk = ntuple_idx(args, I...) ρg[I...] = JustRelax.compute_buoyancy(rheology, args_ijk) @@ -15,6 +21,17 @@ end Calculate the buoyance forces `ρg` for the given GeoParams.jl `rheology` object and correspondent arguments `args`. The `phase_ratios` are used to compute the density of the composite rheology. """ +function compute_ρg!(ρg, phase_ratios::PhaseRatio, rheology, args) # index arguments for the current cell cell center + compute_ρg!(ρg, phase_ratios.center, rheology, args) + return nothing +end + +function compute_ρg!(ρg, phase_ratios_center, rheology, args) # index arguments for the current cell cell center + ni = size(ρg) + @parallel (@idx ni) compute_ρg!(ρg, phase_ratios_center, rheology, args) + return nothing +end + @parallel_indices (I...) function compute_ρg!(ρg, phase_ratios, rheology, args) # index arguments for the current cell cell center args_ijk = ntuple_idx(args, I...) ρg[I...] = JustRelax.compute_buoyancy(rheology, args_ijk, phase_ratios[I...]) diff --git a/src/rheology/Viscosity.jl b/src/rheology/Viscosity.jl index d9089086..df7c5aea 100644 --- a/src/rheology/Viscosity.jl +++ b/src/rheology/Viscosity.jl @@ -1,3 +1,30 @@ +function compute_viscosity!( + η, ν, stokes::StokesArrays, args, rheology, cutoff +) + ni = size(η) + @parallel (@idx ni) compute_viscosity!( + η, ν, @strain(stokes)..., args, rheology, cutoff + ) +end + +function compute_viscosity!( + η, ν, εII::AbstractArray, args, rheology, cutoff +) + ni = size(η) + @parallel (@idx ni) compute_viscosity!( + η, ν, εII, args, rheology, cutoff + ) +end + +function compute_viscosity!( + η, ν, phase_ratios::PhaseRatio, stokes::StokesArrays, args, rheology, cutoff +) + ni = size(η) + @parallel (@idx ni) compute_viscosity!( + η, ν, phase_ratios.center, @strain(stokes)..., args, rheology, cutoff + ) +end + ## 2D KERNELS @parallel_indices (I...) function compute_viscosity!( diff --git a/src/stokes/StressKernels.jl b/src/stokes/StressKernels.jl index e5d22e23..de79d5fc 100644 --- a/src/stokes/StressKernels.jl +++ b/src/stokes/StressKernels.jl @@ -304,7 +304,7 @@ end τ_old, # @ centers ε, # @ vertices ε_pl, # @ centers - EII, # accumulated plastic strain rate @ centers + EII, # accumulated plastic strain rate @ centers P, θ, η, From 2ac031db65de79f98ec46523939a87e69f2b6400 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Tue, 12 Mar 2024 11:43:34 +0100 Subject: [PATCH 10/60] remove redundant memory allocations --- src/stokes/Stokes3D.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index 55d141a9..cbeb8865 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -407,13 +407,13 @@ function JustRelax.solve!( @copy stokes.P0 stokes.P λ = @zeros(ni...) θ = @zeros(ni...) + ητ = deepcopy(η) # solver loop wtime0 = 0.0 while iter < 2 || (err > ϵ && iter ≤ iterMax) wtime0 += @elapsed begin # ~preconditioner - ητ = deepcopy(η) compute_maxloc!(ητ, η) update_halo!(ητ) # @hide_communication b_width begin # communication/computation overlap From cd2eebd68ce003d07a65b951e549806e9cc7ca1b Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 12 Mar 2024 22:59:07 +0100 Subject: [PATCH 11/60] remove dead code --- src/thermal_diffusion/Rheology.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/thermal_diffusion/Rheology.jl b/src/thermal_diffusion/Rheology.jl index 3064c9f0..274f3f06 100644 --- a/src/thermal_diffusion/Rheology.jl +++ b/src/thermal_diffusion/Rheology.jl @@ -32,8 +32,6 @@ end return conductivity * inv(heatcapacity * ρ) end -# ρ*Cp - @inline function compute_ρCp(rheology, args) return compute_heatcapacity(rheology, args) * compute_density(rheology, args) end From 4447d4537b0e22188cb2c7ef8eaf1798db0a302b Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 12 Mar 2024 23:13:25 +0100 Subject: [PATCH 12/60] move init_P! into JR --- src/stokes/PressureKernels.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/stokes/PressureKernels.jl b/src/stokes/PressureKernels.jl index 3adcd350..f6dc9f07 100644 --- a/src/stokes/PressureKernels.jl +++ b/src/stokes/PressureKernels.jl @@ -1,3 +1,25 @@ + +""" + init_P!(P, ρg, z) + +Initialize the pressure field `P` based on the buoyancy forces `ρg` and the vertical coordinate `z`. + +# Arguments +- `P::Array`: Pressure field to be initialized. +- `ρg::Float64`: Buoyancy forces. +- `z::Array`: Vertical coordinate. +""" +function init_P!(P, ρg, z) + ni = size(P) + @parallel (@idx ni) init_P_kernel!(P, ρg, z) + return nothing +end + +@parallel_indices (I...) function init_P_kernel!(P, ρg, z) + P[I...] = abs(ρg[I...] * z[I[end]]) * (z[I[end]] < 0.0) + return nothing +end + # Continuity equation ## Incompressible From fbb7944718be4e3222dbe6f8fa6ac53352bac429 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 12 Mar 2024 23:13:37 +0100 Subject: [PATCH 13/60] typo --- src/phases/phases.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/phases.jl b/src/phases/phases.jl index 72669c21..1b8bd55e 100644 --- a/src/phases/phases.jl +++ b/src/phases/phases.jl @@ -126,7 +126,7 @@ function phase_ratios_center!(phase_ratios::PhaseRatio, particles, xci, di, pPha return nothing end -@parallel_indices (I...) function phase_ratios_center_kernel( +@parallel_indices (I...) function phase_ratios_center( ratio_centers, pxi::NTuple{N,T1}, xci::NTuple{N,T2}, di::NTuple{N,T3}, phases ) where {N,T1,T2,T3} From ee13d268dd301b03de4a99001711ae45309a0864 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 12 Mar 2024 23:14:07 +0100 Subject: [PATCH 14/60] fix import warnings --- src/JustRelax.jl | 2 ++ src/MetaJustRelax.jl | 7 ++----- src/stokes/Stokes2D.jl | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/JustRelax.jl b/src/JustRelax.jl index f1728e46..16675b58 100644 --- a/src/JustRelax.jl +++ b/src/JustRelax.jl @@ -12,6 +12,8 @@ using CellArrays using StaticArrays function solve!() end +function tensor_invariant!() end +function init_P!() end include("topology/Topology.jl") export IGG, lazy_grid, Geometry, velocity_grids, x_g, y_g, z_g diff --git a/src/MetaJustRelax.jl b/src/MetaJustRelax.jl index c5d7131f..d5ee30b2 100644 --- a/src/MetaJustRelax.jl +++ b/src/MetaJustRelax.jl @@ -124,14 +124,11 @@ function environment!(model::PS_Setup{T,N}) where {T,N} include(joinpath(@__DIR__, "rheology/Viscosity.jl")) export compute_viscosity! - # include(joinpath(@__DIR__, "stokes/StressKernels.jl")) - # export tensor_invariant! - include(joinpath(@__DIR__, "stokes/Stokes2D.jl")) - export solve!, tensor_invariant! + export solve!, tensor_invariant!, init_P! include(joinpath(@__DIR__, "stokes/Stokes3D.jl")) - export solve!, tensor_invariant! + export solve!, tensor_invariant!, init_P! include(joinpath(@__DIR__, "thermal_diffusion/DiffusionExplicit.jl")) export ThermalParameters diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 42072ab7..e132372e 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -45,7 +45,7 @@ using ParallelStencil using GeoParams, LinearAlgebra, Printf import JustRelax: elastic_iter_params!, PTArray, Velocity, SymmetricTensor -import JustRelax: tensor_invariant!, compute_τ_nonlinear! +import JustRelax: tensor_invariant!, init_P! import JustRelax: Residual, StokesArrays, PTStokesCoeffs, AbstractStokesModel, ViscoElastic, IGG import JustRelax: compute_maxloc!, solve! @@ -64,7 +64,8 @@ export solve!, rotate_stress_particles_jaumann!, rotate_stress_particles_rotation_matrix!, compute_vorticity!, - tensor_invariant! + tensor_invariant!, + init_P! function update_τ_o!(stokes::StokesArrays{ViscoElastic,A,B,C,D,2}) where {A,B,C,D} τxx, τyy, τxy, τxy_c = stokes.τ.xx, stokes.τ.yy, stokes.τ.xy, stokes.τ.xy_c From b281da273d52e6d9211595902eaa920cbfc83d57 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 12 Mar 2024 23:25:21 +0100 Subject: [PATCH 15/60] couple of tweaks --- src/Utils.jl | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Utils.jl b/src/Utils.jl index 109a52d9..650b3947 100644 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -300,19 +300,6 @@ macro allocate(ni...) return esc(:(PTArray(undef, $(ni...)))) end -function indices(::NTuple{3,T}) where {T} - i = (blockIdx().x - 1) * blockDim().x + threadIdx().x - j = (blockIdx().y - 1) * blockDim().y + threadIdx().y - k = (blockIdx().z - 1) * blockDim().z + threadIdx().z - return i, j, k -end - -function indices(::NTuple{2,T}) where {T} - i = (blockIdx().x - 1) * blockDim().x + threadIdx().x - j = (blockIdx().y - 1) * blockDim().y + threadIdx().y - return i, j -end - """ maxloc!(B, A; window) @@ -407,6 +394,7 @@ Compute the time step `dt` for the velocity field `S.V` and the diffusive maximu dt_adv = mapreduce(x -> x[1] * inv(maximum_mpi(abs.(x[2]))), min, zip(di, V)) * n return min(dt_diff, dt_adv) end + """ compute_dt(S::StokesArrays, di, igg) @@ -510,6 +498,6 @@ for (f1, f2) in zip( ) @eval begin $f1(A::AbstractArray) = $f2(Array(A)) - $f1(A) = $f2(A) + $f1(A::Array) = $f2(A) end end From 9e65a1af1d420e1fa8653cce1db5b9804f1ce2b7 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 12 Mar 2024 23:36:16 +0100 Subject: [PATCH 16/60] revert changes --- src/JustRelax.jl | 2 -- src/MetaJustRelax.jl | 4 ++-- src/stokes/Stokes2D.jl | 7 ++----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/JustRelax.jl b/src/JustRelax.jl index 16675b58..f1728e46 100644 --- a/src/JustRelax.jl +++ b/src/JustRelax.jl @@ -12,8 +12,6 @@ using CellArrays using StaticArrays function solve!() end -function tensor_invariant!() end -function init_P!() end include("topology/Topology.jl") export IGG, lazy_grid, Geometry, velocity_grids, x_g, y_g, z_g diff --git a/src/MetaJustRelax.jl b/src/MetaJustRelax.jl index d5ee30b2..de167fe7 100644 --- a/src/MetaJustRelax.jl +++ b/src/MetaJustRelax.jl @@ -125,10 +125,10 @@ function environment!(model::PS_Setup{T,N}) where {T,N} export compute_viscosity! include(joinpath(@__DIR__, "stokes/Stokes2D.jl")) - export solve!, tensor_invariant!, init_P! + export solve! include(joinpath(@__DIR__, "stokes/Stokes3D.jl")) - export solve!, tensor_invariant!, init_P! + export solve! include(joinpath(@__DIR__, "thermal_diffusion/DiffusionExplicit.jl")) export ThermalParameters diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index e132372e..e06ca149 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -45,7 +45,6 @@ using ParallelStencil using GeoParams, LinearAlgebra, Printf import JustRelax: elastic_iter_params!, PTArray, Velocity, SymmetricTensor -import JustRelax: tensor_invariant!, init_P! import JustRelax: Residual, StokesArrays, PTStokesCoeffs, AbstractStokesModel, ViscoElastic, IGG import JustRelax: compute_maxloc!, solve! @@ -63,9 +62,7 @@ include("StressKernels.jl") export solve!, rotate_stress_particles_jaumann!, rotate_stress_particles_rotation_matrix!, - compute_vorticity!, - tensor_invariant!, - init_P! + compute_vorticity! function update_τ_o!(stokes::StokesArrays{ViscoElastic,A,B,C,D,2}) where {A,B,C,D} τxx, τyy, τxy, τxy_c = stokes.τ.xx, stokes.τ.yy, stokes.τ.xy, stokes.τ.xy_c @@ -621,7 +618,7 @@ function JustRelax.solve!( push!(norm_Rx, errs[1]) push!(norm_Ry, errs[2]) push!(norm_∇V, errs[3]) - err = maximum_mpi(errs) + err = maximum(errs) push!(err_evo1, err) push!(err_evo2, iter) if igg.me == 0 && ((verbose && err > ϵ) || iter == iterMax) From 5d2fcfd03deea47ebcbfae509074a668bb061ee4 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 13 Mar 2024 09:10:26 +0100 Subject: [PATCH 17/60] some updates --- src/rheology/GeoParams.jl | 34 ++++++++++ src/rheology/StressUpdate.jl | 52 +++++++++++++--- src/stokes/Stokes3D.jl | 100 ++++++++++++++++++------------ src/stokes/StressKernels.jl | 117 ++++++++++++++++++++++++++++++++++- 4 files changed, 253 insertions(+), 50 deletions(-) diff --git a/src/rheology/GeoParams.jl b/src/rheology/GeoParams.jl index 90d68eda..0c842227 100644 --- a/src/rheology/GeoParams.jl +++ b/src/rheology/GeoParams.jl @@ -13,3 +13,37 @@ function get_shear_modulus(args...) end return Kb end + +# Check whether the material has constant density. If so, no need to calculate the density +# during PT iterations +@generated function is_constant_density(rheology::NTuple{N, AbstractMaterialParamsStruct}) where N + quote + Base.@_inline_meta + Base.@nexprs $N i -> !_is_constant_density(rheology[i].Density[1]) && return false + return true + end +end + +@inline _is_constant_density(::ConstantDensity) = true +@inline _is_constant_density(::AbstractDensity) = false + +# Check whether the material has a linear viscosity. If so, no need to calculate the viscosity +# during PT iterations +@generated function is_constant_viscosity(rheology::NTuple{N, AbstractMaterialParamsStruct}) where N + quote + Base.@_inline_meta + Base.@nexprs $N i -> is_constant_viscosity(rheology[i].CompositeRheology[1].elements) && return false + return true + end +end + +@generated function is_constant_viscosity(creep_law::NTuple{N, AbstractCreepLaw}) where N + quote + Base.@_inline_meta + Base.@nexprs $N i -> _is_constant_viscosity(creep_law[i]) && return false + return true + end +end + +@inline _is_constant_viscosity(::Union{LinearViscous, ConstantElasticity}) = true +@inline _is_constant_viscosity(::AbstractCreepLaw)= false diff --git a/src/rheology/StressUpdate.jl b/src/rheology/StressUpdate.jl index 23c66f36..af2361a7 100644 --- a/src/rheology/StressUpdate.jl +++ b/src/rheology/StressUpdate.jl @@ -35,7 +35,8 @@ function _compute_τ_nonlinear!( # check if yielding; if so, compute plastic strain rate (λdQdτ), # plastic stress increment (dτ_pl), and update the plastic # multiplier (λ) - dτij, λdQdτ = if isyielding(is_pl, τII_trial, τy) + failure = isyielding(is_pl, τII_trial, τy) + dτij, λdQdτ = if failure # derivatives plastic stress correction dτ_pl, λ[idx...], λdQdτ = compute_dτ_pl( τij, dτij, τy, τII_trial, ηij, λ[idx...], η_reg, dτ_r, volume @@ -48,11 +49,12 @@ function _compute_τ_nonlinear!( end # fill plastic strain rate tensor - update_plastic_strain_rate!(ε_pl, λdQdτ, idx) + failure && update_plastic_strain_rate!(ε_pl, λdQdτ, idx) # update and correct stress - correct_stress!(τ, τij .+ dτij, idx...) + correct_stress!(τ, τij, dτij, idx...) - τII[idx...] = τII_ij = second_invariant(τij...) + # τII[idx...] = + τII_ij = second_invariant(τij...) η_vep[idx...] = τII_ij * 0.5 * inv(second_invariant(εij_ve...)) return nothing @@ -71,14 +73,31 @@ end @inline compute_dτ_r(θ_dτ, ηij, _Gdt) = inv(θ_dτ + fma(ηij, _Gdt, 1.0)) -function compute_stress_increment_and_trial( +# function compute_stress_increment_and_trial( +# τij::NTuple{N,T}, τij_o::NTuple{N,T}, ηij, εij::NTuple{N,T}, _Gdt, dτ_r +# ) where {N,T} +# dτij = ntuple(Val(N)) do i +# Base.@_inline_meta +# dτ_r * fma(2.0 * ηij, εij[i], fma(-((τij[i] - τij_o[i])) * ηij, _Gdt, -τij[i])) +# end +# return dτij, second_invariant((τij .+ dτij)...) +# end + +# Fully unrolled version of the above function. Harder to read but faster +@generated function compute_stress_increment_and_trial( τij::NTuple{N,T}, τij_o::NTuple{N,T}, ηij, εij::NTuple{N,T}, _Gdt, dτ_r ) where {N,T} - dτij = ntuple(Val(N)) do i + quote Base.@_inline_meta - dτ_r * fma(2.0 * ηij, εij[i], fma(-((τij[i] - τij_o[i])) * ηij, _Gdt, -τij[i])) + Base.@nexprs $N i -> begin + τij_n = τij[i] + dτ_i = dτ_r * fma(2.0 * ηij, εij[i], fma(-((τij_n - τij_o[i])) * ηij, _Gdt, -τij_n)) + pt_τ_i = τij_n + dτ_i + end + dτij = Base.@ncall $N tuple dτ + pt_τII = Base.@ncall $N second_invariant pt_τ + return dτij, pt_τII end - return dτij, second_invariant((τij .+ dτij)...) end function compute_dτ_pl( @@ -123,6 +142,23 @@ end return correct_stress!((τxx, τyy, τzz, τyz, τxz, τxy), τij, i, j, k) end +@generated function correct_stress!( + τ, τij::NTuple{N1,T}, dτij::NTuple{N1,T}, idx::Vararg{Integer,N2} +) where {N1,N2,T} + quote + Base.@_inline_meta + Base.@nexprs $N1 i -> τ[i][idx...] = τij[i] + dτij[i] + end +end + +@inline function correct_stress!(τxx, τyy, τxy, τij::NTuple, dτij::NTuple, i, j) + return correct_stress!((τxx, τyy, τxy), τij, dτij, i, j) +end + +@inline function correct_stress!(τxx, τyy, τzz, τyz, τxz, τxy, τij::NTuple, dτij::NTuple, i, j, k) + return correct_stress!((τxx, τyy, τzz, τyz, τxz, τxy), τij, dτij, i, j, k) +end + @inline isplastic(x::AbstractPlasticity) = true @inline isplastic(x) = false diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index cbeb8865..e8929af5 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -409,6 +409,9 @@ function JustRelax.solve!( θ = @zeros(ni...) ητ = deepcopy(η) + cte_density = is_constant_density(rheology) + cte_viscosity = is_constant_viscosity(rheology) + # solver loop wtime0 = 0.0 while iter < 2 || (err > ϵ && iter ≤ iterMax) @@ -439,48 +442,63 @@ function JustRelax.solve!( stokes.∇V, @strain(stokes)..., @velocity(stokes)..., _di... ) - # # Update buoyancy - # @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, args) - - # # Update viscosity - # ν = 1e-2 - # @parallel (@idx ni) compute_viscosity!( - # η, - # ν, - # phase_ratios.center, - # @strain(stokes)..., - # args, - # rheology, - # viscosity_cutoff, - # ) - - @parallel (@idx ni) compute_τ_nonlinear!( - @tensor_center(stokes.τ), - stokes.τ.II, - @tensor_center(stokes.τ_o), - @strain(stokes), - @tensor_center(stokes.ε_pl), - stokes.EII_pl, - stokes.P, - θ, - η, - η_vep, - λ, - phase_ratios.center, - tupleize(rheology), # needs to be a tuple - dt, - pt_stokes.θ_dτ, - ) + # Update buoyancy + if !cte_density + @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, args) + end + if !cte_viscosity + # Update viscosity + ν = 1e-2 + @parallel (@idx ni) compute_viscosity!( + η, + ν, + phase_ratios.center, + @strain(stokes)..., + args, + rheology, + viscosity_cutoff, + ) + end - @parallel (@idx ni .+ 1) center2vertex!( - stokes.τ.yz, - stokes.τ.xz, - stokes.τ.xy, - stokes.τ.yz_c, - stokes.τ.xz_c, - stokes.τ.xy_c, - ) - update_halo!(stokes.τ.yz, stokes.τ.xz, stokes.τ.xy) + # if !cte_viscosity + @parallel (@idx ni) compute_τ_nonlinear!( + @tensor_center(stokes.τ), + stokes.τ.II, + @tensor_center(stokes.τ_o), + @strain(stokes), + @tensor_center(stokes.ε_pl), + stokes.EII_pl, + stokes.P, + θ, + η, + η_vep, + λ, + phase_ratios.center, + tupleize(rheology), # needs to be a tuple + dt, + pt_stokes.θ_dτ, + ) + @parallel (@idx ni .+ 1) center2vertex!( + stokes.τ.yz, + stokes.τ.xz, + stokes.τ.xy, + stokes.τ.yz_c, + stokes.τ.xz_c, + stokes.τ.xy_c, + ) + update_halo!(stokes.τ.yz, stokes.τ.xz, stokes.τ.xy) + # else + # @parallel (@idx ni .+ 1) compute_τ!( + # @tensor(stokes.τ)..., + # @tensor(stokes.τ_o)..., + # @tensor(stokes.ε)..., + # η, + # phase_ratios.center, + # tupleize(rheology), # needs to be a tuple + # dt, + # pt_stokes.θ_dτ, + # ) + # end # @parallel (@idx ni .+ 1) compute_τ_vertex!( # @shear(stokes.τ)..., @shear(stokes.ε)..., η_vep, pt_stokes.θ_dτ diff --git a/src/stokes/StressKernels.jl b/src/stokes/StressKernels.jl index de79d5fc..a60cf0f8 100644 --- a/src/stokes/StressKernels.jl +++ b/src/stokes/StressKernels.jl @@ -118,7 +118,7 @@ end av_xz(A) = _av_xzi(A, i, j, k) av_yz(A) = _av_yzi(A, i, j, k) get(x) = x[i, j, k] - + @inbounds begin if all((i, j, k) .≤ size(τxx)) _Gdt = inv(get(G) * dt) @@ -180,6 +180,121 @@ end return nothing end + +@parallel_indices (i, j, k) function compute_τ!( + τxx, + τyy, + τzz, + τyz, + τxz, + τxy, + τxx_o, + τyy_o, + τzz_o, + τyz_o, + τxz_o, + τxy_o, + εxx, + εyy, + εzz, + εyz, + εxz, + εxy, + η, + phase_center, + rheology, + dt, + θ_dτ, +) + harm_xy(A) = _harm_xyi(A, i, j, k) + harm_xz(A) = _harm_xzi(A, i, j, k) + harm_yz(A) = _harm_yzi(A, i, j, k) + av_xy(A) = _av_xyi(A, i, j, k) + av_xz(A) = _av_xzi(A, i, j, k) + av_yz(A) = _av_yzi(A, i, j, k) + get(x) = x[i, j, k] + + @inbounds begin + if all((i, j, k) .≤ size(τxx)) + phase = phase_center[i, j, k] + _Gdt = inv(fn_ratio(get_shear_modulus, rheology, phase) * dt) + η_ij = get(η) + denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) + # Compute τ_xx + τxx[i, j, k] += + ( + -(get(τxx) - get(τxx_o)) * η_ij * _Gdt - get(τxx) + + 2.0 * η_ij * get(εxx) + ) * denominator + # Compute τ_yy + τyy[i, j, k] += + ( + -(get(τyy) - get(τyy_o)) * η_ij * _Gdt - get(τyy) + + 2.0 * η_ij * get(εyy) + ) * denominator + # Compute τ_zz + τzz[i, j, k] += + ( + -(get(τzz) - get(τzz_o)) * η_ij * _Gdt - get(τzz) + + 2.0 * η_ij * get(εzz) + ) * denominator + end + # Compute τ_xy + if (1 < i < size(τxy, 1)) && (1 < j < size(τxy, 2)) && k ≤ size(τxy, 3) + G = ( + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j-1, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j-1, k]) + ) * 0.25 + _Gdt = inv(G * dt) + η_ij = harm_xy(η) + denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) + τxy[i, j, k] += + ( + -(get(τxy) - get(τxy_o)) * η_ij * _Gdt - get(τxy) + + 2.0 * η_ij * get(εxy) + ) * denominator + end + # Compute τ_xz + if (1 < i < size(τxz, 1)) && j ≤ size(τxz, 2) && (1 < k < size(τxz, 3)) + G = ( + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k-1]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j, k-1]) + ) * 0.25 + _Gdt = inv(G * dt) + η_ij = harm_xz(η) + denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) + τxz[i, j, k] += + ( + -(get(τxz) - get(τxz_o)) * η_ij * _Gdt - get(τxz) + + 2.0 * η_ij * get(εxz) + ) * denominator + end + # Compute τ_yz + if i ≤ size(τyz, 1) && (1 < j < size(τyz, 2)) && (1 < k < size(τyz, 3)) + G = ( + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j-1, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k-1]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j-1, k-1]) + ) * 0.25 + _Gdt = inv(G * dt) + + η_ij = harm_yz(η) + denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) + τyz[i, j, k] += + ( + -(get(τyz) - get(τyz_o)) * η_ij * _Gdt - get(τyz) + + 2.0 * η_ij * get(εyz) + ) * denominator + end + end + return nothing +end + @parallel_indices (i, j, k) function compute_τ_vertex!( τyz, τxz, τxy, εyz, εxz, εxy, ηvep, θ_dτ ) From dc3fc62ac002a4436743414fc5cc65c60b94c2f0 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 14 Mar 2024 12:20:44 +0100 Subject: [PATCH 18/60] use pure GMG to setup the model --- subduction/GMG_setup.jl | 118 ++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 76 deletions(-) diff --git a/subduction/GMG_setup.jl b/subduction/GMG_setup.jl index 77c67ee6..81424e4c 100644 --- a/subduction/GMG_setup.jl +++ b/subduction/GMG_setup.jl @@ -1,77 +1,43 @@ -#= -# 3D Subduction example - -This is a 3D subduction example for `LaMEM.jl` that illustrates how to use the julia interface. -This is very similar to the setup described by Schellart and coworkers in a [2007 nature paper](https://www.nature.com/articles/nature05615) in which they demonstrate that toroidal flow changes the slab curvature during subduction as a function of slab width. -=# - -# ## 1. Generate main model setup -# We first load the packages: -using LaMEM, GeophysicalModelGenerator - -function generate_model() - # Next, we generate the main model setup, specifying the resolution and grid dimensions. - # Note that a range of default values will be set, depending on the parameters you specify. - model = Model( - ## Define the grid - # Grid(nel=(128,32,64), x=[-3960, 500], y=[0,2640], z=[-660 ,0]), - Grid(nel=(43,12,22), x=[-3960, 500], y=[0,2640], z=[-660 ,0]), - - ## No slip lower boundary; the rest is free slip - BoundaryConditions(noslip = [0, 0, 0, 0, 1, 0]), - - ## We use a multigrid solver with 4 levels: - Solver(SolverType="multigrid", MGLevels=1, MGCoarseSolver="mumps", - PETSc_options=[ "-snes_type ksponly", - "-js_ksp_rtol 1e-3", - "-js_ksp_atol 1e-4", - "-js_ksp_monitor"]), - - ## Output filename - Output(out_file_name="Subduction_3D", out_dir="Subduction_3D"), - - ## Timestepping etc - Time(nstep_max=200, nstep_out=5, time_end=100, dt_min=1e-5), - - ## Scaling: - Scaling(GEO_units(length=1km, stress=1e9Pa) ) - ) - - - # ## 2. Define geometry - # Next, we specify the geometry of the model, using the `AddBox!` function from `GeophysicalModelGenerator`. - # We start with the horizontal part of the slab. The function `AddBox!` allows you to specify a layered lithosphere; here we have a crust and mantle. It also allows specifying a thermal structure. - # Since the current setup is only mechanical, we don't specify that here. - AddBox!(model, xlim=(-3000,-1000), ylim=(0,1000), zlim=(-80,0), phase=LithosphericPhases(Layers=[20,60], Phases=[1,2])) - - # The inclined part of the slab is generate by giving it a dip: - AddBox!(model, xlim=(-1000,-810), ylim=(0,1000), zlim=(-80,0), phase=LithosphericPhases(Layers=[20,60], Phases=[1,2]), DipAngle=16) - - # There is a simple way to have a quick look at this setup by using the `Plots.jl` package: - # using Plots - # plot_cross_section(model, y=100, field=:phase) - - # Which will give the following plot: - # ![2D cross section](assets/SubductionSetup_3D.png) - - # ## 3. Add material properties: - # We can specify material properties by using the `Phase` function - mantle = Phase(Name="mantle",ID=0,eta=1e21,rho=3200) - crust = Phase(Name="crust", ID=1,eta=1e21,rho=3280) - slab = Phase(Name="slab", ID=2,eta=2e23,rho=3280) - - # and we can add them to the model with: - add_phase!(model, mantle, slab, crust) - - T = model.Grid.Temp - phases = model.Grid.Phases - - # ni = 128,32,64 - x = -3960e3, 500e3 - y = 0, 2640e3 - z = -660e3, 0 - li = abs(x[2]-x[1]), abs(y[2]-y[1]), abs(z[2]-z[1]) - origin = x[1], y[1], z[1] - - return li, origin, T, phases +using GeophysicalModelGenerator + +function GMG_only(nx, ny, nz) + # nx,ny,nz = 99, 33, 66 + nx, ny, nz = (nx,ny,nz) .+ 1 + x = range(-3960, 500, nx); + y = range(0, 2640, ny); + z = range(-660,0, nz); + Grid = CartData(XYZGrid(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(3, 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); + + # #### Simple free subduction setup + + # Much of the options are explained in the 2D tutorial, which can directly be transferred to 3D. + # Therefore, we will start with a simple subduction setup, which consists of a horizontal part that has a mid-oceanic ridge on one explained + + # We use a lithospheric structure. Note that if the lowermost layer has the same phase as the mantle, you can define `Tlab` as the lithosphere-asthenosphere boundary which will automatically adjust the phase depending on temperature + lith = LithosphericPhases(Layers=[15 45 10], Phases=[1 2 3], Tlab=1250) + AddBox!(Phases, Temp, Grid; xlim=(-3000,-1000), ylim=(0, 1000.0), zlim=(-70.0, 0.0), phase = lith, + Origin=(-0,0,0), + T=SpreadingRateTemp(SpreadingVel=3, MORside="left"), StrikeAngle=0); + + # And an an inclined part: + AddBox!(Phases, Temp, Grid; xlim=(0,300).-1000, ylim=(0, 1000.0), zlim=(-70.0, 0.0), phase = lith, + # Origin=(-1000,0,0), + T=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=3), DipAngle=30, StrikeAngle=0); + # Add them to the `CartData` dataset: + Grid = addField(Grid,(;Phases, Temp)) + + # Which looks like + Write_Paraview(Grid,"Initial_Setup_Subduction"); + + li = abs(last(x)-first(x)), abs(last(y)-first(y)), abs(last(z)-first(z)) + origin = (x[1], y[1], z[1]) .* 1e3 + + return li, origin, Phases, Temp end +ss \ No newline at end of file From e4e4586c345a7d41dc34317c18ee4a50315305ab Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 14 Mar 2024 12:21:11 +0100 Subject: [PATCH 19/60] some switches to speed up the solver --- src/stokes/Stokes3D.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index e8929af5..ffaea2fb 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -411,14 +411,14 @@ function JustRelax.solve!( cte_density = is_constant_density(rheology) cte_viscosity = is_constant_viscosity(rheology) - + do_halo_update = !all(isone, igg.dims) # solver loop wtime0 = 0.0 while iter < 2 || (err > ϵ && iter ≤ iterMax) wtime0 += @elapsed begin # ~preconditioner compute_maxloc!(ητ, η) - update_halo!(ητ) + do_halo_update && update_halo!(ητ) # @hide_communication b_width begin # communication/computation overlap # @parallel compute_maxloc!(ητ, η) # update_halo!(ητ) @@ -486,7 +486,7 @@ function JustRelax.solve!( stokes.τ.xz_c, stokes.τ.xy_c, ) - update_halo!(stokes.τ.yz, stokes.τ.xz, stokes.τ.xy) + do_halo_update && update_halo!(stokes.τ.yz, stokes.τ.xz, stokes.τ.xy) # else # @parallel (@idx ni .+ 1) compute_τ!( # @tensor(stokes.τ)..., @@ -517,7 +517,7 @@ function JustRelax.solve!( ) # apply boundary conditions flow_bcs!(stokes, flow_bc) - update_halo!(@velocity(stokes)...) + do_halo_update && update_halo!(@velocity(stokes)...) end end From 5ae5e160071b48f3f3edba61e6c630d5328f16a4 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 14 Mar 2024 12:21:42 +0100 Subject: [PATCH 20/60] improve setup scripts --- subduction/Subduction3D.jl | 108 ++++++++++++++---------------- subduction/Subduction_rheology.jl | 19 +++--- 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/subduction/Subduction3D.jl b/subduction/Subduction3D.jl index d0bda3f6..0d3fc380 100644 --- a/subduction/Subduction3D.jl +++ b/subduction/Subduction3D.jl @@ -23,6 +23,7 @@ using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays # Load file with all the rheology configurations include("Subduction_rheology.jl") include("GMG_setup.jl") +# include("../toy.jl") ## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- @@ -40,9 +41,9 @@ end ## END OF HELPER FUNCTION ------------------------------------------------------------ ## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- -function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) +function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) - li, origin, T_GMG, phases_GMG = generate_model() + # li, origin, T_GMG, phases_GMG = generate_model() # Physical domain ------------------------------------ # lz = 700e3 # domain length in z @@ -60,7 +61,7 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) # ---------------------------------------------------- # Initialize particles ------------------------------- - nxcell, max_xcell, min_xcell = 25, 35, 8 + nxcell, max_xcell, min_xcell = 40, 60, 20 particles = init_particles( backend, nxcell, max_xcell, min_xcell, xvi..., di..., ni... ) @@ -75,13 +76,13 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) phases_device = PTArray(phases_GMG) init_phases!(pPhases, phases_device, particles, xvi) phase_ratios = PhaseRatio(ni, length(rheology)) - @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) # ---------------------------------------------------- # STOKES --------------------------------------------- # Allocate arrays needed for every Stokes problem stokes = StokesArrays(ni, ViscoElastic) - pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-3, CFL = 0.95 / √3.1) + pt_stokes = PTStokesCoeffs(li, di; ϵ=5e-3, CFL = 0.99 / √3.1) # ---------------------------------------------------- # TEMPERATURE PROFILE -------------------------------- @@ -90,16 +91,18 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) # thermal.T .= T_GMG # @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) # ---------------------------------------------------- - + phase_ratios.center[1,1,1] # Buoyancy forces ρg = ntuple(_ -> @zeros(ni...), Val(3)) - @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, (T=thermal.Tc, P=stokes.P)) - @parallel init_P!(stokes.P, ρg[3], xci[3]) + for _ in 1:3 + compute_ρg!(ρg[3], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + JustRelax.Stokes3D.init_P!(stokes.P, ρg[3], xci[3]) + end # Rheology η = @ones(ni...) args = (; T = thermal.Tc, P = stokes.P, dt = Inf) - @parallel (@idx ni) compute_viscosity!( - η, 1.0, phase_ratios.center, @strain(stokes)..., args, rheology, (1e18, 1e24) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) ) η_vep = deepcopy(η) @@ -126,25 +129,6 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) take(figdir) # ---------------------------------------------------- - # # Plot initial T and η profiles - # fig = let - # Zv = [z for x in xvi[1], y in xvi[2], z in xvi[3]][:] - # Z = [z for x in xci[1], y in xci[2], z in xci[3]][:] - # fig = Figure(size = (1200, 900)) - # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") - # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") - # lines!(ax1, Array(thermal.T[:]), Zv./1e3) - # lines!(ax2, Array(log10.(η[:])), Z./1e3) - # ylims!(ax1, minimum(xvi[3])./1e3, 0) - # ylims!(ax2, minimum(xvi[3])./1e3, 0) - # hideydecorations!(ax2) - # save(joinpath(figdir, "initial_profile.png"), fig) - # fig - # end - - # grid2particle!(pT, xvi, thermal.T, particles) - # dt₀ = similar(stokes.P) - local Vx_v, Vy_v, Vz_v if do_vtk Vx_v = @zeros(ni.+1...) @@ -153,7 +137,7 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) end # Time loop t, it = 0.0, 0 - while it < 150 # run only for 5 Myrs + while it < 1000 # run only for 5 Myrs # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs # # interpolate fields from particle to grid vertices @@ -162,29 +146,34 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) # Update buoyancy and viscosity - args = (; T = thermal.Tc, P = stokes.P, dt=Inf) - @parallel (@idx ni) compute_viscosity!( - η, 1.0, phase_ratios.center, @strain(stokes)..., args, rheology, (1e18, 1e24) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) ) - @parallel (@idx ni) compute_ρg!(ρg[3], phase_ratios.center, rheology, args) - + compute_ρg!(ρg[3], phase_ratios, rheology, args) + # Stokes solver ---------------- - solve!( - stokes, - pt_stokes, - di, - flow_bcs, - ρg, - η, - η_vep, - phase_ratios, - rheology, - args, - Inf, - igg; - iterMax = 100e3, - nout = 1e3, - viscosity_cutoff = (1e18, 1e24) - ); + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + Inf, + igg; + iterMax = 150e3, + nout = 1e3, + viscosity_cutoff = (1e18, 1e24) + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") @parallel (JustRelax.@idx ni) JustRelax.Stokes3D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) dt = compute_dt(stokes, di) # ------------------------------ @@ -295,12 +284,17 @@ function main3D(igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) return nothing end -## END OF MAIN SCRIPT ---------------------------------------------------------------- +## END OF MAIN SCRIPT ---------------------------------------------------------------- do_vtk = true # set to true to generate VTK files for ParaView -nx = 126 -ny = 33 -nz = 63 +# nx = 126 +# ny = 33 +# nz = 63 +# nx = 165 +# ny = 222 +# nz = 54 +nx,ny,nz = 128, 35, 101 +li, origin, phases_GMG, = GMG_only(nx, ny, nz) igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, nz; init_MPI= true)...) else @@ -308,5 +302,5 @@ else end # (Path)/folder where output data and figures are stored -figdir = "Subduction3D" -main3D(igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); +figdir = "Subduction3D_2" +main3D(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); diff --git a/subduction/Subduction_rheology.jl b/subduction/Subduction_rheology.jl index d82c28f4..76f2c59a 100644 --- a/subduction/Subduction_rheology.jl +++ b/subduction/Subduction_rheology.jl @@ -3,27 +3,28 @@ function init_rheologies() # Define rheolgy struct rheology = ( - # Name = "mantle", + # Name = "crust", SetMaterialParams(; Phase = 1, - Density = ConstantDensity(; ρ=3.2e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + Density = ConstantDensity(; ρ=3.28e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), # Elasticity = el_upper_crust, Gravity = ConstantGravity(; g=9.81), ), - # Name = "crust", + # Name = "slab", SetMaterialParams(; Phase = 2, Density = ConstantDensity(; ρ=3.28e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), # Elasticity = el_upper_crust, Gravity = ConstantGravity(; g=9.81), ), - # Name = "slab", + # Name = "mantle", SetMaterialParams(; Phase = 3, - Density = ConstantDensity(; ρ=3.28e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + Density = ConstantDensity(; ρ=3.2e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), # Elasticity = el_upper_crust, Gravity = ConstantGravity(; g=9.81), ), @@ -68,7 +69,7 @@ end particle_phase = phase_grid[ii, jj, kk] end end - JustRelax.@cell phases[ip, I...] = particle_phase + 1.0 + JustRelax.@cell phases[ip, I...] = Float64(particle_phase) end return nothing From 1455548e96ca8944b9089bf1a4b4979c4c62666f Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Fri, 22 Mar 2024 11:50:08 +0100 Subject: [PATCH 21/60] up subduction setup --- subduction/GMG_setup.jl | 7 +++---- subduction/Subduction3D.jl | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/subduction/GMG_setup.jl b/subduction/GMG_setup.jl index 81424e4c..030d0a4b 100644 --- a/subduction/GMG_setup.jl +++ b/subduction/GMG_setup.jl @@ -21,14 +21,14 @@ function GMG_only(nx, ny, nz) # We use a lithospheric structure. Note that if the lowermost layer has the same phase as the mantle, you can define `Tlab` as the lithosphere-asthenosphere boundary which will automatically adjust the phase depending on temperature lith = LithosphericPhases(Layers=[15 45 10], Phases=[1 2 3], Tlab=1250) - AddBox!(Phases, Temp, Grid; xlim=(-3000,-1000), ylim=(0, 1000.0), zlim=(-70.0, 0.0), phase = lith, + AddBox!(Phases, Temp, Grid; xlim=(-3000,-1000), ylim=(0, 1000.0), zlim=(-60.0, 0.0), phase = lith, Origin=(-0,0,0), T=SpreadingRateTemp(SpreadingVel=3, MORside="left"), StrikeAngle=0); # And an an inclined part: - AddBox!(Phases, Temp, Grid; xlim=(0,300).-1000, ylim=(0, 1000.0), zlim=(-70.0, 0.0), phase = lith, + AddBox!(Phases, Temp, Grid; xlim=(0,300).-1000, ylim=(0, 1000.0), zlim=(-60.0, 0.0), phase = lith, # Origin=(-1000,0,0), - T=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=3), DipAngle=30, StrikeAngle=0); + T=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=3), DipAngle=15, StrikeAngle=0); # Add them to the `CartData` dataset: Grid = addField(Grid,(;Phases, Temp)) @@ -40,4 +40,3 @@ function GMG_only(nx, ny, nz) return li, origin, Phases, Temp end -ss \ No newline at end of file diff --git a/subduction/Subduction3D.jl b/subduction/Subduction3D.jl index 0d3fc380..b3464cb8 100644 --- a/subduction/Subduction3D.jl +++ b/subduction/Subduction3D.jl @@ -113,8 +113,8 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, nz=16, figdir="figs3D # Boundary conditions flow_bcs = FlowBoundaryConditions(; - free_slip = (left = true , right = true , top = true , bot = false , front = true , back = true ), - no_slip = (left = false, right = false, top = false, bot = true, front = false, back = false), + free_slip = (left = true , right = true , top = true , bot = false, front = true , back = true ), + no_slip = (left = false, right = false, top = false, bot = true, front = false, back = false), periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), ) flow_bcs!(stokes, flow_bcs) # apply boundary conditions From b177259f7a33e7a25aac0f7daab3bdb7de1bf422 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Fri, 22 Mar 2024 11:50:30 +0100 Subject: [PATCH 22/60] format --- src/IO/VTK.jl | 4 +- src/boundaryconditions/BoundaryConditions.jl | 32 ++++++------ src/phases/phases.jl | 4 +- src/rheology/GeoParams.jl | 17 +++--- src/rheology/StressUpdate.jl | 8 ++- src/rheology/Viscosity.jl | 16 ++---- src/stokes/Stokes3D.jl | 54 ++++++++++---------- src/stokes/StressKernels.jl | 44 ++++++++-------- src/stokes/VelocityKernels.jl | 9 ++-- 9 files changed, 99 insertions(+), 89 deletions(-) diff --git a/src/IO/VTK.jl b/src/IO/VTK.jl index 8680b90c..33d61017 100644 --- a/src/IO/VTK.jl +++ b/src/IO/VTK.jl @@ -41,7 +41,9 @@ function append!(data_series, data::NamedTuple, time_step, seconds) return nothing end -function save_vtk(fname::String, xvi, xci, data_v::NamedTuple, data_c::NamedTuple, velocity::NTuple{N, T}) where {N, T} +function save_vtk( + fname::String, xvi, xci, data_v::NamedTuple, data_c::NamedTuple, velocity::NTuple{N,T} +) where {N,T} # unpack data names and arrays data_names_v = string.(keys(data_v)) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index 4cf2ba4e..637de8e0 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -31,9 +31,9 @@ struct FlowBoundaryConditions{T,nD} <: AbstractBoundaryConditions end @inline bc_index(x::NTuple) = mapreduce(xi -> max(size(xi)...), max, x) -@inline function bc_index(x::NTuple{3, T}) where T +@inline function bc_index(x::NTuple{3,T}) where {T} n = mapreduce(xi -> max(size(xi)...), max, x) - return n,n + return n, n end @inline bc_index(x::T) where {T<:AbstractArray{<:Any,2}} = max(size(x)...) @inline function bc_index(x::T) where {T<:AbstractArray{<:Any,3}} @@ -48,10 +48,12 @@ end Apply the prescribed heat boundary conditions `bc` on the `T` """ -@inline function thermal_bcs!(T::AbstractArray{_T, N}, bcs::TemperatureBoundaryConditions) where {_T, N} +@inline function thermal_bcs!( + T::AbstractArray{_T,N}, bcs::TemperatureBoundaryConditions +) where {_T,N} n = bc_index(T) - for _ in 1:N-1 + for _ in 1:(N - 1) # no flux boundary conditions do_bc(bcs.no_flux) && (@parallel (@idx n) free_slip!(T, bcs.no_flux)) end @@ -59,14 +61,15 @@ Apply the prescribed heat boundary conditions `bc` on the `T` return nothing end -@inline thermal_bcs!(thermal::ThermalArrays, bcs::TemperatureBoundaryConditions) = thermal_bcs!(thermal.T, bcs) +@inline thermal_bcs!(thermal::ThermalArrays, bcs::TemperatureBoundaryConditions) = + thermal_bcs!(thermal.T, bcs) """ flow_bcs!(stokes, bcs::FlowBoundaryConditions, di) Apply the prescribed flow boundary conditions `bc` on the `stokes` """ -function _flow_bcs!(bcs::FlowBoundaryConditions, V::NTuple{N, T}) where {N, T} +function _flow_bcs!(bcs::FlowBoundaryConditions, V::NTuple{N,T}) where {N,T} n = bc_index(V) for _ in 1:2 # no slip boundary conditions @@ -85,7 +88,7 @@ end # BOUNDARY CONDITIONS KERNELS -@parallel_indices (i) function no_slip!(Ax::T, Ay::T, bc) where T +@parallel_indices (i) function no_slip!(Ax::T, Ay::T, bc) where {T} @inbounds begin if bc.bot (i ≤ size(Ax, 2)) && (Ax[1, i] = 0.0) @@ -109,7 +112,7 @@ end return nothing end -@parallel_indices (i, j) function no_slip!(Ax::T, Ay::T, Az::T, bc) where T +@parallel_indices (i, j) function no_slip!(Ax::T, Ay::T, Az::T, bc) where {T} @inbounds begin if bc.bot (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, 1] = -Ax[i, j, 2]) @@ -136,13 +139,12 @@ end (i ≤ size(Az, 1)) && (j ≤ size(Az, 3)) && (Az[i, end, j] = -Az[i, end - 1, j]) end - bc.bot && (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, 1] = 0.0) - bc.top && (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, end] = 0.0) - bc.left && (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[1, i, j] = 0.0) + bc.bot && (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, 1] = 0.0) + bc.top && (i ≤ size(Az, 1)) && (j ≤ size(Az, 2)) && (Az[i, j, end] = 0.0) + bc.left && (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[1, i, j] = 0.0) bc.right && (i ≤ size(Ax, 2)) && (j ≤ size(Ax, 3)) && (Ax[end, i, j] = 0.0) - bc.front && (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, 1, j] = 0.0) - bc.back && (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, end, j] = 0.0) - + bc.front && (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, 1, j] = 0.0) + bc.back && (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 3)) && (Ay[i, end, j] = 0.0) end return nothing end @@ -163,7 +165,7 @@ end @parallel_indices (i, j) function free_slip!(Ax, Ay, Az, bc) @inbounds begin - + # free slip in the top and bottom XY planes if bc.top if i ≤ size(Ax, 1) && j ≤ size(Ax, 2) diff --git a/src/phases/phases.jl b/src/phases/phases.jl index 1b8bd55e..9f6dcc5c 100644 --- a/src/phases/phases.jl +++ b/src/phases/phases.jl @@ -122,7 +122,9 @@ end function phase_ratios_center!(phase_ratios::PhaseRatio, particles, xci, di, pPhases) ni = size(pPhases) - @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + @parallel (@idx ni) phase_ratios_center( + phase_ratios.center, particles.coords, xci, di, pPhases + ) return nothing end diff --git a/src/rheology/GeoParams.jl b/src/rheology/GeoParams.jl index 0c842227..8021dcc5 100644 --- a/src/rheology/GeoParams.jl +++ b/src/rheology/GeoParams.jl @@ -16,7 +16,9 @@ end # Check whether the material has constant density. If so, no need to calculate the density # during PT iterations -@generated function is_constant_density(rheology::NTuple{N, AbstractMaterialParamsStruct}) where N +@generated function is_constant_density( + rheology::NTuple{N,AbstractMaterialParamsStruct} +) where {N} quote Base.@_inline_meta Base.@nexprs $N i -> !_is_constant_density(rheology[i].Density[1]) && return false @@ -29,15 +31,18 @@ end # Check whether the material has a linear viscosity. If so, no need to calculate the viscosity # during PT iterations -@generated function is_constant_viscosity(rheology::NTuple{N, AbstractMaterialParamsStruct}) where N +@generated function is_constant_viscosity( + rheology::NTuple{N,AbstractMaterialParamsStruct} +) where {N} quote Base.@_inline_meta - Base.@nexprs $N i -> is_constant_viscosity(rheology[i].CompositeRheology[1].elements) && return false + Base.@nexprs $N i -> + is_constant_viscosity(rheology[i].CompositeRheology[1].elements) && return false return true end end -@generated function is_constant_viscosity(creep_law::NTuple{N, AbstractCreepLaw}) where N +@generated function is_constant_viscosity(creep_law::NTuple{N,AbstractCreepLaw}) where {N} quote Base.@_inline_meta Base.@nexprs $N i -> _is_constant_viscosity(creep_law[i]) && return false @@ -45,5 +50,5 @@ end end end -@inline _is_constant_viscosity(::Union{LinearViscous, ConstantElasticity}) = true -@inline _is_constant_viscosity(::AbstractCreepLaw)= false +@inline _is_constant_viscosity(::Union{LinearViscous,ConstantElasticity}) = true +@inline _is_constant_viscosity(::AbstractCreepLaw) = false diff --git a/src/rheology/StressUpdate.jl b/src/rheology/StressUpdate.jl index af2361a7..c0e44921 100644 --- a/src/rheology/StressUpdate.jl +++ b/src/rheology/StressUpdate.jl @@ -91,7 +91,9 @@ end Base.@_inline_meta Base.@nexprs $N i -> begin τij_n = τij[i] - dτ_i = dτ_r * fma(2.0 * ηij, εij[i], fma(-((τij_n - τij_o[i])) * ηij, _Gdt, -τij_n)) + dτ_i = + dτ_r * + fma(2.0 * ηij, εij[i], fma(-((τij_n - τij_o[i])) * ηij, _Gdt, -τij_n)) pt_τ_i = τij_n + dτ_i end dτij = Base.@ncall $N tuple dτ @@ -155,7 +157,9 @@ end return correct_stress!((τxx, τyy, τxy), τij, dτij, i, j) end -@inline function correct_stress!(τxx, τyy, τzz, τyz, τxz, τxy, τij::NTuple, dτij::NTuple, i, j, k) +@inline function correct_stress!( + τxx, τyy, τzz, τyz, τxz, τxy, τij::NTuple, dτij::NTuple, i, j, k +) return correct_stress!((τxx, τyy, τzz, τyz, τxz, τxy), τij, dτij, i, j, k) end diff --git a/src/rheology/Viscosity.jl b/src/rheology/Viscosity.jl index df7c5aea..7f230d3b 100644 --- a/src/rheology/Viscosity.jl +++ b/src/rheology/Viscosity.jl @@ -1,19 +1,11 @@ -function compute_viscosity!( - η, ν, stokes::StokesArrays, args, rheology, cutoff -) +function compute_viscosity!(η, ν, stokes::StokesArrays, args, rheology, cutoff) ni = size(η) - @parallel (@idx ni) compute_viscosity!( - η, ν, @strain(stokes)..., args, rheology, cutoff - ) + @parallel (@idx ni) compute_viscosity!(η, ν, @strain(stokes)..., args, rheology, cutoff) end -function compute_viscosity!( - η, ν, εII::AbstractArray, args, rheology, cutoff -) +function compute_viscosity!(η, ν, εII::AbstractArray, args, rheology, cutoff) ni = size(η) - @parallel (@idx ni) compute_viscosity!( - η, ν, εII, args, rheology, cutoff - ) + @parallel (@idx ni) compute_viscosity!(η, ν, εII, args, rheology, cutoff) end function compute_viscosity!( diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index 2d779c07..8410f592 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -416,7 +416,7 @@ function JustRelax.solve!( θ = @zeros(ni...) ητ = deepcopy(η) - cte_density = is_constant_density(rheology) + cte_density = is_constant_density(rheology) cte_viscosity = is_constant_viscosity(rheology) do_halo_update = !all(isone, igg.dims) # solver loop @@ -468,32 +468,32 @@ function JustRelax.solve!( end # if !cte_viscosity - @parallel (@idx ni) compute_τ_nonlinear!( - @tensor_center(stokes.τ), - stokes.τ.II, - @tensor_center(stokes.τ_o), - @strain(stokes), - @tensor_center(stokes.ε_pl), - stokes.EII_pl, - stokes.P, - θ, - η, - η_vep, - λ, - phase_ratios.center, - tupleize(rheology), # needs to be a tuple - dt, - pt_stokes.θ_dτ, - ) - @parallel (@idx ni .+ 1) center2vertex!( - stokes.τ.yz, - stokes.τ.xz, - stokes.τ.xy, - stokes.τ.yz_c, - stokes.τ.xz_c, - stokes.τ.xy_c, - ) - do_halo_update && update_halo!(stokes.τ.yz, stokes.τ.xz, stokes.τ.xy) + @parallel (@idx ni) compute_τ_nonlinear!( + @tensor_center(stokes.τ), + stokes.τ.II, + @tensor_center(stokes.τ_o), + @strain(stokes), + @tensor_center(stokes.ε_pl), + stokes.EII_pl, + stokes.P, + θ, + η, + η_vep, + λ, + phase_ratios.center, + tupleize(rheology), # needs to be a tuple + dt, + pt_stokes.θ_dτ, + ) + @parallel (@idx ni .+ 1) center2vertex!( + stokes.τ.yz, + stokes.τ.xz, + stokes.τ.xy, + stokes.τ.yz_c, + stokes.τ.xz_c, + stokes.τ.xy_c, + ) + do_halo_update && update_halo!(stokes.τ.yz, stokes.τ.xz, stokes.τ.xy) # else # @parallel (@idx ni .+ 1) compute_τ!( # @tensor(stokes.τ)..., diff --git a/src/stokes/StressKernels.jl b/src/stokes/StressKernels.jl index a60cf0f8..c44ddd28 100644 --- a/src/stokes/StressKernels.jl +++ b/src/stokes/StressKernels.jl @@ -118,7 +118,7 @@ end av_xz(A) = _av_xzi(A, i, j, k) av_yz(A) = _av_yzi(A, i, j, k) get(x) = x[i, j, k] - + @inbounds begin if all((i, j, k) .≤ size(τxx)) _Gdt = inv(get(G) * dt) @@ -180,7 +180,6 @@ end return nothing end - @parallel_indices (i, j, k) function compute_τ!( τxx, τyy, @@ -241,12 +240,13 @@ end end # Compute τ_xy if (1 < i < size(τxy, 1)) && (1 < j < size(τxy, 2)) && k ≤ size(τxy, 3) - G = ( - fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i, j-1, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j-1, k]) - ) * 0.25 + G = + ( + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i - 1, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j - 1, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i - 1, j - 1, k]) + ) * 0.25 _Gdt = inv(G * dt) η_ij = harm_xy(η) denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) @@ -258,12 +258,13 @@ end end # Compute τ_xz if (1 < i < size(τxz, 1)) && j ≤ size(τxz, 2) && (1 < k < size(τxz, 3)) - G = ( - fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k-1]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i-1, j, k-1]) - ) * 0.25 + G = + ( + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i - 1, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k - 1]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i - 1, j, k - 1]) + ) * 0.25 _Gdt = inv(G * dt) η_ij = harm_xz(η) denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) @@ -275,14 +276,15 @@ end end # Compute τ_yz if i ≤ size(τyz, 1) && (1 < j < size(τyz, 2)) && (1 < k < size(τyz, 3)) - G = ( - fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i, j-1, k]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k-1]) + - fn_ratio(get_shear_modulus, rheology, phase_center[i, j-1, k-1]) - ) * 0.25 + G = + ( + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j - 1, k]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j, k - 1]) + + fn_ratio(get_shear_modulus, rheology, phase_center[i, j - 1, k - 1]) + ) * 0.25 _Gdt = inv(G * dt) - + η_ij = harm_yz(η) denominator = inv(θ_dτ + η_ij * _Gdt + 1.0) τyz[i, j, k] += diff --git a/src/stokes/VelocityKernels.jl b/src/stokes/VelocityKernels.jl index c77a5ce8..96d49d0d 100644 --- a/src/stokes/VelocityKernels.jl +++ b/src/stokes/VelocityKernels.jl @@ -156,10 +156,11 @@ end Vx[i + 1, j + 1, k + 1] += Rx_ijk * ηdτ / av_x(ητ) end if all((i, j, k) .< size(Vy) .- 1) - Ry_ijk = Ry[i, j, k] - _dx * (τxy[i + 1, j + 1, k] - τxy[i, j + 1, k]) + - _dy * (τyy[i, j + 1, k] - τyy[i, j, k]) + - _dz * (τyz[i, j + 1, k + 1] - τyz[i, j + 1, k]) - d_ya(P) - av_y(fy) + Ry_ijk = + Ry[i, j, k] = + _dx * (τxy[i + 1, j + 1, k] - τxy[i, j + 1, k]) + + _dy * (τyy[i, j + 1, k] - τyy[i, j, k]) + + _dz * (τyz[i, j + 1, k + 1] - τyz[i, j + 1, k]) - d_ya(P) - av_y(fy) Vy[i + 1, j + 1, k + 1] += Ry_ijk * ηdτ / av_y(ητ) end if all((i, j, k) .< size(Vz) .- 1) From 7ad0927fa8b18455f08560af5e25e4dc8e8a56b3 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Mon, 25 Mar 2024 17:35:57 +0100 Subject: [PATCH 23/60] fix merge weird conflicts --- src/boundaryconditions/BoundaryConditions.jl | 28 +--- test/test_boundary_conditions2D.jl | 166 ++++++++++--------- 2 files changed, 85 insertions(+), 109 deletions(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index 36ad64cb..a3eff122 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -89,7 +89,7 @@ end # BOUNDARY CONDITIONS KERNELS -@parallel_indices (i) function no_slip!(Ax::T, Ay::T, bc) where {T} +@parallel_indices (i) function no_slip!(Ax, Ay, bc) @inbounds begin if bc.left (i ≤ size(Ax, 2)) && (Ax[1, i] = 0.0) @@ -115,32 +115,6 @@ end return nothing end -@parallel_indices (i, j) function no_slip!(Ax::T, Ay::T, Az::T, bc) where {T} - @inbounds begin - if bc.bot - (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, 1] = -Ax[i, j, 2]) - (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 2)) && (Ay[i, j, 1] = -Ay[i, j, 2]) - end - if bc.top - (i ≤ size(Ax, 1)) && (j ≤ size(Ax, 2)) && (Ax[i, j, end] = -Ax[i, j, end - 1]) - (i ≤ size(Ay, 1)) && (j ≤ size(Ay, 2)) && (Ay[i, j, end] = -Ay[i, j, end - 1]) - end - if bc.left - (i ≤ size(Ay, 1)) && (Ay[i, 1] = 0.0) - (1 < i < size(Ax, 1)) && (Ax[i, 1] = -Ax[i, 2]) - end - if bc.right - (i ≤ size(Ay, 1)) && (Ay[i, end] = 0.0) - (1 < i < size(Ax, 1)) && (Ax[i, end] = -Ax[i, end - 1]) - end - # corners - # bc.bot && (Ax[1, 1] = 0.0; Ax[1, 1] = 0.0) - # bc.left && bc.bot && (Ax[1, 1] = 0.0) - # bc.right && bc.top && (Ay[end, end] = 0.0) - end - return nothing -end - @parallel_indices (i, j) function no_slip!(Ax::T, Ay::T, Az::T, bc) where {T} @inbounds begin if bc.bot diff --git a/test/test_boundary_conditions2D.jl b/test/test_boundary_conditions2D.jl index 6c8c018f..f9bab7bd 100644 --- a/test/test_boundary_conditions2D.jl +++ b/test/test_boundary_conditions2D.jl @@ -1,101 +1,103 @@ -using Test, Suppressor +using Test using JustRelax model = PS_Setup(:cpu, Float64, 2) environment!(model) @testset "Flow boundary conditions 2D" begin - # periodicity - n = 5 # number of elements - Vx, Vy = PTArray(rand(n + 1, n + 2)), PTArray(rand(n + 2, n + 1)) - bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=true, right=true, top=true, bot=true), - ) - flow_bcs!(bcs, Vx, Vy) - - @test @views Vx[: , 1] == Vx[: , end - 1] - @test @views Vx[: , end] == Vx[: , 2] - @test @views Vy[1 , :] == Vy[end - 1, :] - @test @views Vy[end, :] == Vy[2 , :] + n = 5 # number of elements + Vx, Vy = PTArray(rand(n + 1, n + 2)), PTArray(rand(n + 2, n + 1)) + + # free-slip + bcs = FlowBoundaryConditions(; + no_slip=(left=false, right=false, top=false, bot=false), + free_slip=(left=true, right=true, top=true, bot=true), + periodicity=(left=false, right=false, top=false, bot=false), + ) + flow_bcs!(bcs, Vx, Vy) - # free-slip - bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=true, right=true, top=true, bot=true), - periodicity=(left=false, right=false, top=false, bot=false), - ) - flow_bcs!(bcs, Vx, Vy) + @test @views Vx[:, 1] == Vx[:, 2] + @test @views Vx[:, end] == Vx[:, end - 1] + @test @views Vy[1, :] == Vy[2, :] + @test @views Vy[end, :] == Vy[end - 1, :] - @test @views Vx[:, 1] == Vx[:, 2] - @test @views Vx[:, end] == Vx[:, end - 1] - @test @views Vy[1, :] == Vy[2, :] - @test @views Vy[end, :] == Vy[end - 1, :] + # no-slip + Vx, Vy = PTArray(rand(n + 1, n + 2)), PTArray(rand(n + 2, n + 1)) + bcs = FlowBoundaryConditions(; + no_slip=(left=true, right=true, top=true, bot=true), + free_slip=(left=false, right=false, top=false, bot=false), + periodicity=(left=false, right=false, top=false, bot=false), + ) + flow_bcs!(bcs, Vx, Vy) + @test sum(!iszero(Vx[1 , i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(Vx[end, i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test @views Vy[1 , :] == -Vy[2 , :] + @test @views Vy[end, :] == -Vy[end - 1, :] + @test @views Vx[: , 1] == -Vx[: , 2] + @test @views Vx[: , end] == -Vx[: , end - 1] - # no-slip - Vx, Vy = PTArray(rand(n + 1, n + 2)), PTArray(rand(n + 2, n + 1)) - bcs = FlowBoundaryConditions(; - no_slip=(left=true, right=true, top=true, bot=true), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=false, right=false, top=false, bot=false), - ) - flow_bcs!(bcs, Vx, Vy) - @test sum(!iszero(Vx[1 , i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(Vx[end, i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test sum(!iszero(Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test @views Vy[1 , :] == -Vy[2 , :] - @test @views Vy[end, :] == -Vy[end - 1, :] - @test @views Vx[: , 1] == -Vx[: , 2] - @test @views Vx[: , end] == -Vx[: , end - 1] - - # test with StokesArrays - # periodicity - ni = 5, 5 - stokes = StokesArrays(ni, ViscoElastic) - stokes.V.Vx .= PTArray(rand(n + 1, n + 2)) - stokes.V.Vy .= PTArray(rand(n + 2, n + 1)) - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=true, right=true, top=true, bot=true), - ) - flow_bcs!(stokes, flow_bcs) + # test with StokesArrays + # periodicity + ni = 5, 5 + stokes = StokesArrays(ni, ViscoElastic) + stokes.V.Vx .= PTArray(rand(n + 1, n + 2)) + stokes.V.Vy .= PTArray(rand(n + 2, n + 1)) + flow_bcs = FlowBoundaryConditions(; + no_slip=(left=false, right=false, top=false, bot=false), + free_slip=(left=false, right=false, top=false, bot=false), + periodicity=(left=true, right=true, top=true, bot=true), + ) + flow_bcs!(stokes, flow_bcs) - @test @views stokes.V.Vx[:, 1] == stokes.V.Vx[:, end - 1] - @test @views stokes.V.Vx[:, end] == stokes.V.Vx[:, 2] - @test @views stokes.V.Vy[1, :] == stokes.V.Vy[end - 1, :] - @test @views stokes.V.Vy[end, :] == stokes.V.Vy[2, :] + @test @views stokes.V.Vx[:, 1] == stokes.V.Vx[:, end - 1] + @test @views stokes.V.Vx[:, end] == stokes.V.Vx[:, 2] + @test @views stokes.V.Vy[1, :] == stokes.V.Vy[end - 1, :] + @test @views stokes.V.Vy[end, :] == stokes.V.Vy[2, :] - # free-slip - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=false, right=false, top=false, bot=false), - free_slip=(left=true, right=true, top=true, bot=true), - periodicity=(left=false, right=false, top=false, bot=false), - ) - flow_bcs!(stokes, flow_bcs) + # free-slip + flow_bcs = FlowBoundaryConditions(; + no_slip=(left=false, right=false, top=false, bot=false), + free_slip=(left=true, right=true, top=true, bot=true), + periodicity=(left=false, right=false, top=false, bot=false), + ) + flow_bcs!(stokes, flow_bcs) @test @views stokes.V.Vx[ :, 1] == stokes.V.Vx[ :, 2] @test @views stokes.V.Vx[ :, end] == stokes.V.Vx[ :, end - 1] @test @views stokes.V.Vy[ 1, :] == stokes.V.Vy[ 2, :] @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] - # no-slip - flow_bcs = FlowBoundaryConditions(; - no_slip=(left=true, right=true, top=true, bot=true), - free_slip=(left=false, right=false, top=false, bot=false), - periodicity=(left=false, right=false, top=false, bot=false), - ) - flow_bcs!(stokes, flow_bcs) + # no-slip + flow_bcs = FlowBoundaryConditions(; + no_slip=(left=true, right=true, top=true, bot=true), + free_slip=(left=false, right=false, top=false, bot=false), + periodicity=(left=false, right=false, top=false, bot=false), + ) + flow_bcs!(stokes, flow_bcs) + + @test sum(!iszero(stokes.V.Vx[1 , i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(stokes.V.Vx[end, i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test @views stokes.V.Vy[1 , :] == -stokes.V.Vy[2 , :] + @test @views stokes.V.Vy[end, :] == -stokes.V.Vy[end - 1, :] + @test @views stokes.V.Vx[: , 1] == -stokes.V.Vx[: , 2] + @test @views stokes.V.Vx[: , end] == -stokes.V.Vx[: , end - 1] +end + +@testset "Temperature boundary conditions 2D" begin + ni = 5, 5 # number of elements + thermal = ThermalArrays(ni) + # free-slip + bcs = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = true, bot = true), + ) + thermal_bcs!(thermal, bcs) - @test sum(!iszero(stokes.V.Vx[1 , i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(stokes.V.Vx[end, i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test @views stokes.V.Vy[1 , :] == -stokes.V.Vy[2 , :] - @test @views stokes.V.Vy[end, :] == -stokes.V.Vy[end - 1, :] - @test @views stokes.V.Vx[: , 1] == -stokes.V.Vx[: , 2] - @test @views stokes.V.Vx[: , end] == -stokes.V.Vx[: , end - 1] - end + @test @views thermal.T[ :, 1] == thermal.T[ :, 2] + @test @views thermal.T[ :, end] == thermal.T[ :, end - 1] + @test @views thermal.T[ 1, :] == thermal.T[ 2, :] + @test @views thermal.T[end, :] == thermal.T[end - 1, :] end From 2437a75b3c2e5bd9d06f792127eacd1b452e66b7 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Mon, 25 Mar 2024 17:39:02 +0100 Subject: [PATCH 24/60] update GMG syntax --- subduction/GMG_setup.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/subduction/GMG_setup.jl b/subduction/GMG_setup.jl index 030d0a4b..bb6a384a 100644 --- a/subduction/GMG_setup.jl +++ b/subduction/GMG_setup.jl @@ -6,7 +6,7 @@ function GMG_only(nx, ny, nz) x = range(-3960, 500, nx); y = range(0, 2640, ny); z = range(-660,0, nz); - Grid = CartData(XYZGrid(x,y,z)); + 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(3, nx, ny, nz); @@ -14,26 +14,26 @@ function GMG_only(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); - # #### Simple free subduction setup + #### Simple free subduction setup # Much of the options are explained in the 2D tutorial, which can directly be transferred to 3D. # Therefore, we will start with a simple subduction setup, which consists of a horizontal part that has a mid-oceanic ridge on one explained # We use a lithospheric structure. Note that if the lowermost layer has the same phase as the mantle, you can define `Tlab` as the lithosphere-asthenosphere boundary which will automatically adjust the phase depending on temperature lith = LithosphericPhases(Layers=[15 45 10], Phases=[1 2 3], Tlab=1250) - AddBox!(Phases, Temp, Grid; xlim=(-3000,-1000), ylim=(0, 1000.0), zlim=(-60.0, 0.0), phase = lith, + add_box!(Phases, Temp, Grid; xlim=(-3000,-1000), ylim=(0, 1000.0), zlim=(-60.0, 0.0), phase = lith, Origin=(-0,0,0), T=SpreadingRateTemp(SpreadingVel=3, MORside="left"), StrikeAngle=0); # And an an inclined part: - AddBox!(Phases, Temp, Grid; xlim=(0,300).-1000, ylim=(0, 1000.0), zlim=(-60.0, 0.0), phase = lith, + add_box!(Phases, Temp, Grid; xlim=(0,300).-1000, ylim=(0, 1000.0), zlim=(-60.0, 0.0), phase = lith, # Origin=(-1000,0,0), T=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=3), DipAngle=15, StrikeAngle=0); # Add them to the `CartData` dataset: - Grid = addField(Grid,(;Phases, Temp)) + Grid = addfield(Grid,(;Phases, Temp)) # Which looks like - Write_Paraview(Grid,"Initial_Setup_Subduction"); + write_paraview(Grid,"Initial_Setup_Subduction"); li = abs(last(x)-first(x)), abs(last(y)-first(y)), abs(last(z)-first(z)) origin = (x[1], y[1], z[1]) .* 1e3 From 60ba57540862fcfeea9727e9a73131219583c078 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Fri, 5 Apr 2024 17:50:59 +0200 Subject: [PATCH 25/60] 2D subduction miniapps --- subduction/GMG_setup2D.jl | 82 ++++++ subduction/Subduction2D.jl | 304 +++++++++++++++++++++ subduction/Subduction3D_debug.jl | 396 ++++++++++++++++++++++++++++ subduction/Subduction_rheology.jl | 127 +++++++-- subduction/Subduction_rheology2D.jl | 76 ++++++ 5 files changed, 964 insertions(+), 21 deletions(-) create mode 100644 subduction/GMG_setup2D.jl create mode 100644 subduction/Subduction2D.jl create mode 100644 subduction/Subduction3D_debug.jl create mode 100644 subduction/Subduction_rheology2D.jl diff --git a/subduction/GMG_setup2D.jl b/subduction/GMG_setup2D.jl new file mode 100644 index 00000000..230f84ce --- /dev/null +++ b/subduction/GMG_setup2D.jl @@ -0,0 +1,82 @@ + +#= +# Creating 2D numerical model setups + +### Aim +The aim of this tutorial is to show you how to create 2D numerical model setups that can be used as initial setups for other codes. + +=# + + +#= +### 2D Subduction setup + +Lets start with creating a 2D model setup in cartesian coordinates, which uses the `CartData` data structure +=# +using GeophysicalModelGenerator + +function GMG_subduction_2D(nx, ny) + # Our starting basis is the example above with ridge and overriding slab + nx, nz = nx, ny + x = range(-1000, 1000, nx); + z = range(-660,0, nz); + Grid2D = CartData(xyz_grid(x,0,z)) + Phases = zeros(Int64, nx, 1, nz); + Temp = fill(1350.0, nx, 1, nz); + lith = LithosphericPhases(Layers=[15 20 55], Phases=[3 4 5], Tlab=1250) + # mantle = LithosphericPhases(Phases=[1]) + + # add_box!(Phases, Temp, Grid2D; xlim=(-1000, 1000), zlim=(-600.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + # Phases .= 0 + + # Lets start with defining the horizontal part of the overriding plate. + # Note that we define this twice with different thickness to deal with the bending subduction area: + add_box!(Phases, Temp, Grid2D; xlim=(200,1000), zlim=(-150.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + add_box!(Phases, Temp, Grid2D; xlim=(0,200), zlim=(-50.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + + # The horizontal part of the oceanic plate is as before: + v_spread_cm_yr = 3 #spreading velocity + lith = LithosphericPhases(Layers=[15 55], Phases=[1 2], Tlab=1250) + add_box!(Phases, Temp, Grid2D; xlim=(-1000,0.0), zlim=(-150.0, 0.0), phase = lith, T=SpreadingRateTemp(SpreadingVel=v_spread_cm_yr)); + + # Yet, now we add a trench as well. The starting thermal age at the trench is that of the horizontal part of the oceanic plate: + AgeTrench_Myrs = 1000e3/(v_spread_cm_yr/1e2)/1e6 #plate age @ trench + + # We want to add a smooth transition from a halfspace cooling 1D thermal profile to a slab that is heated by the surrounding mantle below a decoupling depth `d_decoupling`. + T_slab = LinearWeightedTemperature( F1=HalfspaceCoolingTemp(Age=AgeTrench_Myrs), F2=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=v_spread_cm_yr, Adiabat = 0.0)) + + # in this case, we have a more reasonable slab thickness: + trench = Trench(Start=(0.0,-100.0), End=(0.0,100.0), Thickness=100.0, θ_max=30.0, Length=600, Lb=200, + WeakzoneThickness=15, WeakzonePhase=6, d_decoupling=125); + add_slab!(Phases, Temp, Grid2D, trench, phase = lith, T=T_slab); + + # Lithosphere-asthenosphere boundary: + ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); + Phases[ind] .= 0; + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + + li = abs(last(x)-first(x)), abs(last(z)-first(z)) + origin = (x[1], z[1]) .* 1e3 + + ph = Phases[:,1,:] .+ 1 + ph2 = ph .== 2 + ph3 = ph .== 3 + ph4 = ph .== 4 + ph5 = ph .== 5 + ph6 = ph .== 6 + ph7 = ph .== 7 + ph[ph2] .= 1 + ph[ph3] .= 1 + ph[ph4] .= 2 + ph[ph5] .= 3 + ph[ph6] .= 1 + ph[ph7] .= 4 + + return li, origin, ph, Temp[:,1,:] +end + + +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) +f,ax,h=heatmap(phases_GMG) +Colorbar(f[1,2], h); f \ No newline at end of file diff --git a/subduction/Subduction2D.jl b/subduction/Subduction2D.jl new file mode 100644 index 00000000..cf4908b6 --- /dev/null +++ b/subduction/Subduction2D.jl @@ -0,0 +1,304 @@ +using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +@init_parallel_stencil(CUDA, Float64, 2) + +using JustPIC +using JustPIC._2D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +# model = PS_Setup(:Threads, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("Subduction_rheology2D.jl") +include("GMG_setup2D.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[2] +macro all_k(A) + esc(:($A[$idx_k])) +end + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) + + # Physical domain ------------------------------------ + ni = nx, ny # number of cells + di = @. li / ni # grid steps + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies() + dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi, di, ni + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pPhases, pT = init_cell_arrays(particles, Val(2)) + particle_args = (pPhases, pT) + + # Assign particles phases anomaly + phases_device = PTArray(phases_GMG .+ 1) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=5e-3, CFL = 0.99 / √2.1) + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + thermal = ThermalArrays(ni) + # thermal_bc = TemperatureBoundaryConditions() + @views thermal.T[2:end-1, :] .= PTArray(T_GMG) + # @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(2)) + for _ in 1:3 + compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + JustRelax.Stokes3D.init_P!(stokes.P, ρg[2], xci[2]) + end + # Rheology + η = @ones(ni...) + η_vep = similar(η) + args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + + # # PT coefficients for thermal diffusion + # pt_thermal = PTThermalCoeffs( + # rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 + # ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = false), + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(@velocity(stokes)...) + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + end + # Time loop + t, it = 0.0, 0 + while it < 1000 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # # interpolate fields from particle to grid vertices + # particle2grid!(thermal.T, pT, xvi, particles) + # temperature2center!(thermal) + + # Update buoyancy and viscosity - + args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + compute_ρg!(ρg[2], phase_ratios, rheology, args) + + # Stokes solver ---------------- + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + Inf, + igg; + iterMax = 150e3, + nout = 1e3, + viscosity_cutoff = (1e18, 1e24) + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") + @parallel (JustRelax.@idx ni) JustRelax.Stokes3D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + dt = compute_dt(stokes, di) + # ------------------------------ + + # # Thermal solver --------------- + # heatdiffusion_PT!( + # thermal, + # pt_thermal, + # thermal_bc, + # rheology, + # args, + # dt, + # di; + # igg = igg, + # phase = phase_ratios, + # iterMax = 10e3, + # nout = 1e2, + # verbose = true, + # ) + # subgrid_characteristic_time!( + # subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + # ) + # centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + # subgrid_diffusion!( + # pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + # ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection_RK!(particles, @velocity(stokes), grid_vx, grid_vy, grid_vz, dt, 2 / 3) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject = check_injection(particles) + inject && inject_particles_phase!(particles, pPhases, tuple(), tuple(), xvi) + # update phase ratios + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 5) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, Vz_v, @velocity(stokes)...) + data_v = (; + T = Array(thermal.T), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + Vz = Array(Vz_v), + ) + data_c = (; + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(η), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + Array(Vz_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + slice_j = ny >>> 1 + # Make Makie figure + fig = Figure(size = (1400, 1800), title = "t = $t") + ax1 = Axis(fig[1,1], title = "P [GPA] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], title = "τII [MPa]") + ax3 = Axis(fig[1,3], title = "log10(εII)") + ax4 = Axis(fig[2,3], title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[3].*1e-3, Array(stokes.P[:, slice_j, :]./1e9) , colormap=:lajolla) + # Plot particles phase + h2 = heatmap!(ax2, xci[1].*1e-3, xci[3].*1e-3, Array(stokes.τ.II[:, slice_j, :].*1e-6) , colormap=:batlow) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(stokes.ε.II[:, slice_j, :])) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(η[:, slice_j, :])) , colormap=:batlow) + hideydecorations!(ax3) + hideydecorations!(ax4) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end + +## END OF MAIN SCRIPT ---------------------------------------------------------------- +do_vtk = true # set to true to generate VTK files for ParaView +# nx = 126 +# ny = 33 +# nz = 63 +# nx = 165 +# ny = 222 +# nz = 54 +nx, ny = 512, 128 +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) + +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) +else + igg +end + +# # (Path)/folder where output data and figures are stored +# figdir = "Subduction3D_2" +# # main3D(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); + + +f,ax,h=heatmap(phases_GMG) +Colorbar(f[1,2], h); f \ No newline at end of file diff --git a/subduction/Subduction3D_debug.jl b/subduction/Subduction3D_debug.jl new file mode 100644 index 00000000..c79c20ea --- /dev/null +++ b/subduction/Subduction3D_debug.jl @@ -0,0 +1,396 @@ +# using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +@init_parallel_stencil(Threads, Float64, 3) + +using JustPIC +using JustPIC._3D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +model = PS_Setup(:Threads, Float64, 3) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:CUDA, Float64, 3) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("Subduction_rheology.jl") +include("GMG_setup.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[3] +macro all_k(A) + esc(:($A[$idx_k])) +end + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, nz=16, figdir="figs3D", do_vtk =false) + + # li, origin, T_GMG, phases_GMG = generate_model() + + # Physical domain ------------------------------------ + # lz = 700e3 # domain length in z + # lx = ly = lz # domain length in x and y + ni = nx, ny, nz # number of cells + di = @. li / ni # grid steps + # origin = 0.0, 0.0, -lz # origin coordinates (15km of sticky air layer) + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies() + dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell, max_xcell, min_xcell = 25, 35, 8 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi..., di..., ni... + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vx, grid_vy, grid_vz = velocity_grids(xci, xvi, di) + # temperature + pPhases, = init_cell_arrays(particles, Val(1)) + particle_args = (pPhases, ) + + # Assign particles phases anomaly + phases_device = PTArray(phases_GMG) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-3, CFL = 0.95 / √3.1) + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + thermal = ThermalArrays(ni) + # thermal_bc = TemperatureBoundaryConditions() + # thermal.T .= T_GMG + # @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(3)) + compute_ρg!(ρg[3], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + @parallel init_P!(stokes.P, ρg[3], xci[3]) + # Rheology + η = @ones(ni...) + args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + η_vep = deepcopy(η) + + # # PT coefficients for thermal diffusion + # pt_thermal = PTThermalCoeffs( + # rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 + # ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = false , front = true , back = true ), + no_slip = (left = false, right = false, top = false, bot = true, front = false, back = false), + periodicity = (left = false, right = false, top = false, bot = false, front = false, back = false), + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(stokes.V.Vx, stokes.V.Vy, stokes.V.Vz) + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = figdir*"\\vtk" + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + # # Plot initial T and η profiles + # fig = let + # Zv = [z for x in xvi[1], y in xvi[2], z in xvi[3]][:] + # Z = [z for x in xci[1], y in xci[2], z in xci[3]][:] + # fig = Figure(size = (1200, 900)) + # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + # lines!(ax1, Array(thermal.T[:]), Zv./1e3) + # lines!(ax2, Array(log10.(η[:])), Z./1e3) + # ylims!(ax1, minimum(xvi[3])./1e3, 0) + # ylims!(ax2, minimum(xvi[3])./1e3, 0) + # hideydecorations!(ax2) + # save(joinpath(figdir, "initial_profile.png"), fig) + # fig + # end + + # grid2particle!(pT, xvi, thermal.T, particles) + # dt₀ = similar(stokes.P) + + local Vx_v, Vy_v, Vz_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + Vz_v = @zeros(ni.+1...) + end + # Time loop + t, it = 0.0, 0 + while it < 150 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # # interpolate fields from particle to grid vertices + # particle2grid!(thermal.T, pT, xvi, particles) + # temperature2center!(thermal) + + # Update buoyancy and viscosity - + args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + compute_ρg!(ρg[3], phase_ratios, rheology, args) + + # Stokes solver ---------------- + solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + Inf, + igg; + iterMax = 1, + nout = 1, + viscosity_cutoff = (1e18, 1e24) + ); + @parallel (JustRelax.@idx ni) JustRelax.Stokes3D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + dt = compute_dt(stokes, di) + # ------------------------------ + + # # Thermal solver --------------- + # heatdiffusion_PT!( + # thermal, + # pt_thermal, + # thermal_bc, + # rheology, + # args, + # dt, + # di; + # igg = igg, + # phase = phase_ratios, + # iterMax = 10e3, + # nout = 1e2, + # verbose = true, + # ) + # subgrid_characteristic_time!( + # subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + # ) + # centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + # subgrid_diffusion!( + # pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + # ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection_RK!(particles, @velocity(stokes), grid_vx, grid_vy, grid_vz, dt, 2 / 3) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject = check_injection(particles) + inject && inject_particles_phase!(particles, pPhases, tuple(), tuple(), xvi) + # update phase ratios + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 5) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, Vz_v, @velocity(stokes)...) + data_v = (; + T = Array(thermal.T), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + Vz = Array(Vz_v), + ) + data_c = (; + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(η), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + Array(Vz_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + slice_j = ny >>> 1 + # Make Makie figure + fig = Figure(size = (1400, 1800), title = "t = $t") + ax1 = Axis(fig[1,1], title = "P [GPA] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], title = "τII [MPa]") + ax3 = Axis(fig[1,3], title = "log10(εII)") + ax4 = Axis(fig[2,3], title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[3].*1e-3, Array(stokes.P[:, slice_j, :]./1e9) , colormap=:lajolla) + # Plot particles phase + h2 = heatmap!(ax2, xci[1].*1e-3, xci[3].*1e-3, Array(stokes.τ.II[:, slice_j, :].*1e-6) , colormap=:batlow) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(stokes.ε.II[:, slice_j, :])) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(η[:, slice_j, :])) , colormap=:batlow) + hideydecorations!(ax3) + hideydecorations!(ax4) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end +## END OF MAIN SCRIPT ---------------------------------------------------------------- + +do_vtk = true # set to true to generate VTK files for ParaView +nx = 126 +ny = 33 +nz = 63 +li, origin, T_GMG, phases_GMG = generate_model() +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, nz; init_MPI= true)...) +else + igg +end + +# (Path)/folder where output data and figures are stored +figdir = "Subduction3D" +main3D(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); + +# @code_warntype main3D(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); + +ProfileCanvas.@profview begin + solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + Inf, + igg; + iterMax = 1e2, + nout = 1e2, + viscosity_cutoff = (1e18, 1e24) + ); +end + +# environment!(model) +# @code_warntype solve!( +# stokes, +# pt_stokes, +# di, +# flow_bcs, +# ρg, +# η, +# η_vep, +# phase_ratios, +# rheology, +# args, +# Inf, +# igg; +# iterMax = 2, +# nout = 1, +# viscosity_cutoff = (1e18, 1e24) +# ); + +# foo(stokes) = @copy stokes.P0 stokes.P +# @descend foo(stokes) + + +τij = rand(), rand(), rand(), rand(), rand(), rand() +τij_o = rand(), rand(), rand(), rand(), rand(), rand() +εij = rand(), rand(), rand(), rand(), rand(), rand() +ηij = rand() +_Gdt = rand() +dτ_r = rand() + +function foo( + τij::NTuple{N,T}, τij_o::NTuple{N,T}, ηij, εij::NTuple{N,T}, _Gdt, dτ_r +) where {N,T} + dτij = ntuple(Val(N)) do i + Base.@_inline_meta + dτ_r * fma(2.0 * ηij, εij[i], fma(-((τij[i] - τij_o[i])) * ηij, _Gdt, -τij[i])) + end + return dτij, second_invariant((τij .+ dτij)...) +end + +@b foo($(τij, τij_o, ηij, εij, _Gdt, dτ_r)...) # 3.228ns + +@generated function foo2( + τij::NTuple{N,T}, τij_o::NTuple{N,T}, ηij, εij::NTuple{N,T}, _Gdt, dτ_r +) where {N,T} + quote + Base.@_inline_meta + Base.@nexprs $N i -> begin + τij_n = τij[i] + dτ_i = dτ_r * fma(2.0 * ηij, εij[i], fma(-((τij_n - τij_o[i])) * ηij, _Gdt, -τij_n)) + pt_τ_i = τij_n + dτ_i + end + dτij = Base.@ncall $N tuple dτ + pt_τII = Base.@ncall $N second_invariant pt_τ + return dτij, pt_τII + end +end +@be foo2($(τij, τij_o, ηij, εij, _Gdt, dτ_r)...) # 2.99ns + +foo2(τij, τij_o, ηij, εij, _Gdt, dτ_r) == foo2(τij, τij_o, ηij, εij, _Gdt, dτ_r) diff --git a/subduction/Subduction_rheology.jl b/subduction/Subduction_rheology.jl index 76f2c59a..754d6655 100644 --- a/subduction/Subduction_rheology.jl +++ b/subduction/Subduction_rheology.jl @@ -1,32 +1,119 @@ # from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 + + function init_rheologies() + disl_dry_olivine = DislocationCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) + disl_wet_olivine = DislocationCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) + disl_wet_quartzite = DislocationCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) + disl_plagioclase = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + disl_gabbro = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + + diff_dry_olivine = DiffusionCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) + diff_wet_olivine = DiffusionCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) + diff_wet_quartzite = DiffusionCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) + diff_plagioclase = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + diff_gabbro = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + + ϕ_dry_olivine = asind(0.6) + ϕ_wet_olivine = asind(0.1) + ϕ_wet_quartzite = asind(0.3) + ϕ_plagioclase = asind(0.3) + + # common physical properties + α = 3e-5 # 1 / K + Cp = 1000 # J / kg K + C = 3e6 # Pa + η_reg = 1e16 + # Define rheolgy struct rheology = ( - # Name = "crust", + # Name = "dry ol - lithospheric mantle", SetMaterialParams(; Phase = 1, - Density = ConstantDensity(; ρ=3.28e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + Density = PT_Density(; ρ0=3.3e3, α = α, T0 = 273), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + DruckerPrager_regularised(; C = C, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(0.022), # Elasticity = el_upper_crust, Gravity = ConstantGravity(; g=9.81), ), - # Name = "slab", + # # Name = "gabbro - oceanic lithosphere", + # SetMaterialParams(; + # Phase = 2, + # Density = PT_Density(; ρ0=3e3, α = α, T0 = 273), + # CompositeRheology = CompositeRheology( + # ( + # disl_gabro, + # diff_gabro, + # DruckerPrager_regularised(; C = C, ϕ=ϕ_gabbro, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + # ) + # ), + # RadioactiveHeat = ConstantRadioactiveHeat(0.022), + # # Elasticity = el_upper_crust, + # Gravity = ConstantGravity(; g=9.81), + # ), + # # Name = "lower slab", + # SetMaterialParams(; + # Phase = 3, + # Density = ConstantDensity(; ρ=3.28e3), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # # Elasticity = el_upper_crust, + # ), + # Name = "wet qtz - upper continental crust", SetMaterialParams(; Phase = 2, - Density = ConstantDensity(; ρ=3.28e3), - # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), - CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + Density = PT_Density(; ρ0=2.75e3, α = α, T0 = 273), + CompositeRheology = CompositeRheology( + ( + disl_wet_quartzite, + diff_wet_quartzite, + DruckerPrager_regularised(; C = C, ϕ=ϕ_wet_quartzite, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2), # Elasticity = el_upper_crust, - Gravity = ConstantGravity(; g=9.81), ), - # Name = "mantle", + # Name = "plagioclase - lower continental crust", SetMaterialParams(; Phase = 3, - Density = ConstantDensity(; ρ=3.2e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + Density = PT_Density(; ρ0=3e3, α = α, T0 = 273), + CompositeRheology = CompositeRheology( + ( + disl_plagioclase, + diff_plagioclase, + DruckerPrager_regularised(; C = C, ϕ=ϕ_plagioclase, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(0.2), + # Elasticity = el_upper_crust, + ), + # # Name = "lithosphere", + # SetMaterialParams(; + # Phase = 6, + # Density = PT_Density(; ρ0=3.3e3, α = α, T0 = 273), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # # Elasticity = el_upper_crust, + # ), + # Name = "wet ol - weak zone", + SetMaterialParams(; + Phase = 4, + Density = PT_Density(; ρ0=3.2e3, α = α, T0 = 273), + RadioactiveHeat = ConstantRadioactiveHeat(0.022), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + CompositeRheology = CompositeRheology( + ( + disl_wet_olivine, + diff_wet_olivine, + DruckerPrager_regularised(; C = C, ϕ=ϕ_wet_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), # Elasticity = el_upper_crust, - Gravity = ConstantGravity(; g=9.81), ), ) end @@ -42,34 +129,32 @@ end for ip in JustRelax.cellaxes(phases) # quick escape - JustRelax.@cell(index[ip, I...]) == 0 && continue + @cell(index[ip, I...]) == 0 && continue pᵢ = ntuple(Val(N)) do i - JustRelax.@cell pcoords[i][ip, I...] + @cell pcoords[i][ip, I...] end d = Inf # distance to the nearest particle particle_phase = -1 - for offi in 0:1, offj in 0:1, offk in 0:1 - ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk + for offi in 0:1, offj in 0:1 + ii = I[1] + offi + jj = I[2] + offj !(ii ≤ ni[1]) && continue !(jj ≤ ni[2]) && continue - !(kk ≤ ni[3]) && continue xvᵢ = ( xvi[1][ii], xvi[2][jj], - xvi[3][kk], ) - # @show xvᵢ ii jj kk d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) if d_ijk < d d = d_ijk - particle_phase = phase_grid[ii, jj, kk] + particle_phase = phase_grid[ii, jj] end end - JustRelax.@cell phases[ip, I...] = Float64(particle_phase) + @cell phases[ip, I...] = Float64(particle_phase) end return nothing diff --git a/subduction/Subduction_rheology2D.jl b/subduction/Subduction_rheology2D.jl new file mode 100644 index 00000000..76f2c59a --- /dev/null +++ b/subduction/Subduction_rheology2D.jl @@ -0,0 +1,76 @@ +# from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 + +function init_rheologies() + # Define rheolgy struct + rheology = ( + # Name = "crust", + SetMaterialParams(; + Phase = 1, + Density = ConstantDensity(; ρ=3.28e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "slab", + SetMaterialParams(; + Phase = 2, + Density = ConstantDensity(; ρ=3.28e3), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "mantle", + SetMaterialParams(; + Phase = 3, + Density = ConstantDensity(; ρ=3.2e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + JustRelax.@cell(index[ip, I...]) == 0 && continue + + pᵢ = ntuple(Val(N)) do i + JustRelax.@cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1, offk in 0:1 + ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + !(kk ≤ ni[3]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + xvi[3][kk], + ) + # @show xvᵢ ii jj kk + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj, kk] + end + end + JustRelax.@cell phases[ip, I...] = Float64(particle_phase) + end + + return nothing +end \ No newline at end of file From 74fa165c3264847987d55f58a800ef6619ca7643 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Mon, 8 Apr 2024 15:07:15 +0200 Subject: [PATCH 26/60] up 2D subduction miniapp --- Subduction_GMG_2D.jl | 58 +++++++++ subduction/GMG_setup2D.jl | 31 +++-- subduction/Subduction2D.jl | 189 +++++++++++++++++----------- subduction/Subduction_rheology.jl | 161 ------------------------ subduction/Subduction_rheology2D.jl | 149 ++++++++++++++++++---- subduction/Subduction_rheology3D.jl | 76 +++++++++++ 6 files changed, 397 insertions(+), 267 deletions(-) create mode 100644 Subduction_GMG_2D.jl delete mode 100644 subduction/Subduction_rheology.jl create mode 100644 subduction/Subduction_rheology3D.jl diff --git a/Subduction_GMG_2D.jl b/Subduction_GMG_2D.jl new file mode 100644 index 00000000..b40cc1fb --- /dev/null +++ b/Subduction_GMG_2D.jl @@ -0,0 +1,58 @@ +#= +# Creating 2D numerical model setups + +### Aim +The aim of this tutorial is to show you how to create 2D numerical model setups that can be used as initial setups for other codes. + +=# + + +#= +### 2D Subduction setup + +Lets start with creating a 2D model setup in cartesian coordinates, which uses the `CartData` data structure +=# +using GeophysicalModelGenerator + +function GMG_subduction_2D(nx, ny) + # Our starting basis is the example above with ridge and overriding slab + nx, nz = nx, ny + x = range(-1000,1000, nx); + z = range(-660,0, nz); + Grid2D = CartData(xyz_grid(x,0,z)) + Phases = zeros(Int64, nx, 1, nz); + Temp = fill(1350.0, nx, 1, nz); + lith = LithosphericPhases(Layers=[15 20 55], Phases=[3 4 5], Tlab=1250) + # mantle = LithosphericPhases(Phases=[1]) + + # add_box!(Phases, Temp, Grid2D; xlim=(-1000, 1000), zlim=(-600.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + # Phases .= 0 + + # Lets start with defining the horizontal part of the overriding plate. Note that we define this twice with different thickness to deal with the bending subduction area: + add_box!(Phases, Temp, Grid2D; xlim=(200,1000), zlim=(-150.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + add_box!(Phases, Temp, Grid2D; xlim=(0,200), zlim=(-50.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + + # The horizontal part of the oceanic plate is as before: + v_spread_cm_yr = 3 #spreading velocity + lith = LithosphericPhases(Layers=[15 55], Phases=[1 2], Tlab=1250) + add_box!(Phases, Temp, Grid2D; xlim=(-1000,0.0), zlim=(-150.0, 0.0), phase = lith, T=SpreadingRateTemp(SpreadingVel=v_spread_cm_yr)); + + # Yet, now we add a trench as well. The starting thermal age at the trench is that of the horizontal part of the oceanic plate: + AgeTrench_Myrs = 1000e3/(v_spread_cm_yr/1e2)/1e6 #plate age @ trench + + # We want to add a smooth transition from a halfspace cooling 1D thermal profile to a slab that is heated by the surrounding mantle below a decoupling depth `d_decoupling`. + T_slab = LinearWeightedTemperature( F1=HalfspaceCoolingTemp(Age=AgeTrench_Myrs), F2=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=v_spread_cm_yr, Adiabat = 0.0)) + + # in this case, we have a more reasonable slab thickness: + trench = Trench(Start=(0.0,-100.0), End=(0.0,100.0), Thickness=100.0, θ_max=30.0, Length=600, Lb=200, + WeakzoneThickness=15, WeakzonePhase=6, d_decoupling=125); + add_slab!(Phases, Temp, Grid2D, trench, phase = lith, T=T_slab); + + # Lithosphere-asthenosphere boundary: + ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); + Phases[ind] .= 0; + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + +end + \ No newline at end of file diff --git a/subduction/GMG_setup2D.jl b/subduction/GMG_setup2D.jl index 230f84ce..5bd10d44 100644 --- a/subduction/GMG_setup2D.jl +++ b/subduction/GMG_setup2D.jl @@ -19,11 +19,12 @@ function GMG_subduction_2D(nx, ny) # Our starting basis is the example above with ridge and overriding slab nx, nz = nx, ny x = range(-1000, 1000, nx); - z = range(-660,0, nz); + z = range(-660, 20, nz); Grid2D = CartData(xyz_grid(x,0,z)) Phases = zeros(Int64, nx, 1, nz); Temp = fill(1350.0, nx, 1, nz); - lith = LithosphericPhases(Layers=[15 20 55], Phases=[3 4 5], Tlab=1250) + air_thickness = 20.0 + lith = LithosphericPhases(Layers=[air_thickness+15 20 55], Phases=[3 4 5], Tlab=1250) # mantle = LithosphericPhases(Phases=[1]) # add_box!(Phases, Temp, Grid2D; xlim=(-1000, 1000), zlim=(-600.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); @@ -32,7 +33,7 @@ function GMG_subduction_2D(nx, ny) # Lets start with defining the horizontal part of the overriding plate. # Note that we define this twice with different thickness to deal with the bending subduction area: add_box!(Phases, Temp, Grid2D; xlim=(200,1000), zlim=(-150.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); - add_box!(Phases, Temp, Grid2D; xlim=(0,200), zlim=(-50.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + add_box!(Phases, Temp, Grid2D; xlim=(0,200), zlim=(-50.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); # The horizontal part of the oceanic plate is as before: v_spread_cm_yr = 3 #spreading velocity @@ -43,10 +44,13 @@ function GMG_subduction_2D(nx, ny) AgeTrench_Myrs = 1000e3/(v_spread_cm_yr/1e2)/1e6 #plate age @ trench # We want to add a smooth transition from a halfspace cooling 1D thermal profile to a slab that is heated by the surrounding mantle below a decoupling depth `d_decoupling`. - T_slab = LinearWeightedTemperature( F1=HalfspaceCoolingTemp(Age=AgeTrench_Myrs), F2=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=v_spread_cm_yr, Adiabat = 0.0)) + T_slab = LinearWeightedTemperature( + F1=HalfspaceCoolingTemp(Age=AgeTrench_Myrs), + F2=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=v_spread_cm_yr, Adiabat = 0.0) + ) # in this case, we have a more reasonable slab thickness: - trench = Trench(Start=(0.0,-100.0), End=(0.0,100.0), Thickness=100.0, θ_max=30.0, Length=600, Lb=200, + trench = Trench(Start=(0.0, -100.0), End=(0.0, 100.0), Thickness=100.0, θ_max=30.0, Length=300, Lb=200, WeakzoneThickness=15, WeakzonePhase=6, d_decoupling=125); add_slab!(Phases, Temp, Grid2D, trench, phase = lith, T=T_slab); @@ -54,29 +58,30 @@ function GMG_subduction_2D(nx, ny) ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); Phases[ind] .= 0; + surf = Grid2D.z.val .> 0.0 + Temp[surf] .= 0.0 + Phases[surf] .= 7 + Grid2D = addfield(Grid2D,(;Phases, Temp)) - li = abs(last(x)-first(x)), abs(last(z)-first(z)) + li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 origin = (x[1], z[1]) .* 1e3 - ph = Phases[:,1,:] .+ 1 + ph = Phases[:,1,:] .+ 1; ph2 = ph .== 2 ph3 = ph .== 3 ph4 = ph .== 4 ph5 = ph .== 5 ph6 = ph .== 6 ph7 = ph .== 7 + ph8 = ph .== 8 ph[ph2] .= 1 ph[ph3] .= 1 ph[ph4] .= 2 ph[ph5] .= 3 ph[ph6] .= 1 ph[ph7] .= 4 + ph[ph8] .= 5 - return li, origin, ph, Temp[:,1,:] + return li, origin, ph, Temp[:,1,:].+273 end - - -li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) -f,ax,h=heatmap(phases_GMG) -Colorbar(f[1,2], h); f \ No newline at end of file diff --git a/subduction/Subduction2D.jl b/subduction/Subduction2D.jl index cf4908b6..2a718ff7 100644 --- a/subduction/Subduction2D.jl +++ b/subduction/Subduction2D.jl @@ -32,6 +32,16 @@ macro all_k(A) esc(:($A[$idx_k])) end +function copyinn_x!(A, B) + @parallel function f_x(A, B) + @all(A) = @inn_x(B) + return nothing + end + + @parallel f_x(A, B) +end + + # Initial pressure profile - not accurate @parallel function init_P!(P, ρg, z) @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) @@ -40,7 +50,7 @@ end ## END OF HELPER FUNCTION ------------------------------------------------------------ ## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- -function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) +function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) # Physical domain ------------------------------------ ni = nx, ny # number of cells @@ -69,7 +79,7 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v particle_args = (pPhases, pT) # Assign particles phases anomaly - phases_device = PTArray(phases_GMG .+ 1) + phases_device = PTArray(phases_GMG) init_phases!(pPhases, phases_device, particles, xvi) phase_ratios = PhaseRatio(ni, length(rheology)) phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) @@ -78,21 +88,24 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v # STOKES --------------------------------------------- # Allocate arrays needed for every Stokes problem stokes = StokesArrays(ni, ViscoElastic) - pt_stokes = PTStokesCoeffs(li, di; ϵ=5e-3, CFL = 0.99 / √2.1) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.9 / √2.1) # ---------------------------------------------------- # TEMPERATURE PROFILE -------------------------------- thermal = ThermalArrays(ni) - # thermal_bc = TemperatureBoundaryConditions() @views thermal.T[2:end-1, :] .= PTArray(T_GMG) - # @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = false, bot = false), + ) + thermal_bcs!(thermal, thermal_bc) + @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) # ---------------------------------------------------- # Buoyancy forces ρg = ntuple(_ -> @zeros(ni...), Val(2)) - for _ in 1:3 + for _ in 1:2 compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) - JustRelax.Stokes3D.init_P!(stokes.P, ρg[2], xci[2]) + JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) end # Rheology η = @ones(ni...) @@ -102,18 +115,36 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) ) - # # PT coefficients for thermal diffusion - # pt_thermal = PTThermalCoeffs( - # rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 - # ) + # PT coefficients for thermal diffusion + pt_thermal = PTThermalCoeffs( + rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-2 / √3 + ) # Boundary conditions flow_bcs = FlowBoundaryConditions(; - free_slip = (left = true , right = true , top = true , bot = false), + free_slip = (left = true , right = true , top = true , bot = true), + free_surface = true, ) flow_bcs!(stokes, flow_bcs) # apply boundary conditions update_halo!(@velocity(stokes)...) + # Plot initial T and η profiles + let + Yv = [y for x in xvi[1], y in xvi[2]][:] + Y = [y for x in xci[1], y in xci[2]][:] + fig = Figure(size = (1200, 900)) + ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # scatter!(ax2, Array(ρg[2][:]), Y./1e3) + ylims!(ax1, minimum(xvi[2])./1e3, 0) + ylims!(ax2, minimum(xvi[2])./1e3, 0) + hideydecorations!(ax2) + save(joinpath(figdir, "initial_profile.png"), fig) + fig + end + # IO ------------------------------------------------- # if it does not exist, make folder where figures are stored if do_vtk @@ -128,15 +159,31 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v Vx_v = @zeros(ni.+1...) Vy_v = @zeros(ni.+1...) end + + T_buffer = @zeros(ni.+1) + Told_buffer = similar(T_buffer) + dt₀ = similar(stokes.P) + for (dst, src) in zip((T_buffer, Told_buffer), (thermal.T, thermal.Told)) + copyinn_x!(dst, src) + end + grid2particle!(pT, xvi, T_buffer, particles) + # Time loop t, it = 0.0, 0 while it < 1000 # run only for 5 Myrs # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs - # # interpolate fields from particle to grid vertices + # interpolate fields from particle to grid vertices + particle2grid!(T_buffer, pT, xvi, particles) + @views T_buffer[:, end] .= 273.0 + @views T_buffer[:, 1] .= 1623.0 + @views thermal.T[2:end-1, :] .= T_buffer + thermal_bcs!(thermal, thermal_bc) + temperature2center!(thermal) + + # interpolate fields from particle to grid vertices # particle2grid!(thermal.T, pT, xvi, particles) # temperature2center!(thermal) - # Update buoyancy and viscosity - args = (; T = thermal.Tc, P = stokes.P, dt=Inf) compute_viscosity!( @@ -157,52 +204,54 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v phase_ratios, rheology, args, - Inf, + dt, igg; iterMax = 150e3, - nout = 1e3, - viscosity_cutoff = (1e18, 1e24) + nout = 2e3, + viscosity_cutoff = (1e18, 1e24), + free_surface = true, + # viscosity_relaxation = 1e-5 ); end println("Stokes solver time ") println(" Total time: $t_stokes s") println(" Time/iteration: $(t_stokes / out.iter) s") - @parallel (JustRelax.@idx ni) JustRelax.Stokes3D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) dt = compute_dt(stokes, di) # ------------------------------ - # # Thermal solver --------------- - # heatdiffusion_PT!( - # thermal, - # pt_thermal, - # thermal_bc, - # rheology, - # args, - # dt, - # di; - # igg = igg, - # phase = phase_ratios, - # iterMax = 10e3, - # nout = 1e2, - # verbose = true, - # ) - # subgrid_characteristic_time!( - # subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di - # ) - # centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) - # subgrid_diffusion!( - # pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt - # ) + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology, + args, + dt, + di; + igg = igg, + phase = phase_ratios, + iterMax = 10e3, + nout = 1e2, + verbose = true, + ) + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) # ------------------------------ # Advection -------------------- # advect particles in space - advection_RK!(particles, @velocity(stokes), grid_vx, grid_vy, grid_vz, dt, 2 / 3) + advection_RK!(particles, @velocity(stokes), grid_vxi..., dt, 2 / 3) # advect particles in memory move_particles!(particles, xvi, particle_args) # check if we need to inject particles inject = check_injection(particles) - inject && inject_particles_phase!(particles, pPhases, tuple(), tuple(), xvi) + inject && inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer,), xvi) # update phase ratios @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) @@ -214,14 +263,13 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v checkpointing(figdir, stokes, thermal.T, η, t) if do_vtk - JustRelax.velocity2vertex!(Vx_v, Vy_v, Vz_v, @velocity(stokes)...) + JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) data_v = (; T = Array(thermal.T), τxy = Array(stokes.τ.xy), εxy = Array(stokes.ε.xy), Vx = Array(Vx_v), Vy = Array(Vy_v), - Vz = Array(Vz_v), ) data_c = (; P = Array(stokes.P), @@ -234,7 +282,6 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v velocity_v = ( Array(Vx_v), Array(Vy_v), - Array(Vz_v), ) save_vtk( joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), @@ -246,23 +293,33 @@ function main3D(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_v ) end - slice_j = ny >>> 1 + # Make particles plottable + p = particles.coords + ppx, ppy = p + pxv = ppx.data[:]./1e3 + pyv = ppy.data[:]./1e3 + clr = pPhases.data[:] + # clr = pT.data[:] + idxv = particles.index.data[:]; + # Make Makie figure - fig = Figure(size = (1400, 1800), title = "t = $t") - ax1 = Axis(fig[1,1], title = "P [GPA] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") - ax2 = Axis(fig[2,1], title = "τII [MPa]") - ax3 = Axis(fig[1,3], title = "log10(εII)") - ax4 = Axis(fig[2,3], title = "log10(η)") + ar = 3 + fig = Figure(size = (1200, 900), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "T [K] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "Vy [m/s]") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") # Plot temperature - h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[3].*1e-3, Array(stokes.P[:, slice_j, :]./1e9) , colormap=:lajolla) + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) # Plot particles phase - h2 = heatmap!(ax2, xci[1].*1e-3, xci[3].*1e-3, Array(stokes.τ.II[:, slice_j, :].*1e-6) , colormap=:batlow) + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) # Plot 2nd invariant of strain rate - h3 = heatmap!(ax3, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(stokes.ε.II[:, slice_j, :])) , colormap=:batlow) + h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) # Plot effective viscosity - h4 = heatmap!(ax4, xci[1].*1e-3, xci[3].*1e-3, Array(log10.(η[:, slice_j, :])) , colormap=:batlow) - hideydecorations!(ax3) - hideydecorations!(ax4) + h4 = heatmap!(ax4, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η_vep)) , colormap=:batlow) + hidexdecorations!(ax1) + hidexdecorations!(ax2) + hidexdecorations!(ax3) Colorbar(fig[1,2], h1) Colorbar(fig[2,2], h2) Colorbar(fig[1,4], h3) @@ -280,25 +337,15 @@ end ## END OF MAIN SCRIPT ---------------------------------------------------------------- do_vtk = true # set to true to generate VTK files for ParaView -# nx = 126 -# ny = 33 -# nz = 63 -# nx = 165 -# ny = 222 -# nz = 54 -nx, ny = 512, 128 +figdir = "Subduction2D" +# nx, ny = 512, 256 +nx, ny = 512, 128 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) - igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) else igg end -# # (Path)/folder where output data and figures are stored -# figdir = "Subduction3D_2" -# # main3D(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, nz = nz, do_vtk = do_vtk); - +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); -f,ax,h=heatmap(phases_GMG) -Colorbar(f[1,2], h); f \ No newline at end of file diff --git a/subduction/Subduction_rheology.jl b/subduction/Subduction_rheology.jl deleted file mode 100644 index 754d6655..00000000 --- a/subduction/Subduction_rheology.jl +++ /dev/null @@ -1,161 +0,0 @@ -# from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 - - - -function init_rheologies() - disl_dry_olivine = DislocationCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) - disl_wet_olivine = DislocationCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) - disl_wet_quartzite = DislocationCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) - disl_plagioclase = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) - disl_gabbro = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) - - diff_dry_olivine = DiffusionCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) - diff_wet_olivine = DiffusionCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) - diff_wet_quartzite = DiffusionCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) - diff_plagioclase = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) - diff_gabbro = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) - - ϕ_dry_olivine = asind(0.6) - ϕ_wet_olivine = asind(0.1) - ϕ_wet_quartzite = asind(0.3) - ϕ_plagioclase = asind(0.3) - - # common physical properties - α = 3e-5 # 1 / K - Cp = 1000 # J / kg K - C = 3e6 # Pa - η_reg = 1e16 - - # Define rheolgy struct - rheology = ( - # Name = "dry ol - lithospheric mantle", - SetMaterialParams(; - Phase = 1, - Density = PT_Density(; ρ0=3.3e3, α = α, T0 = 273), - CompositeRheology = CompositeRheology( - ( - disl_dry_olivine, - diff_dry_olivine, - DruckerPrager_regularised(; C = C, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(0.022), - # Elasticity = el_upper_crust, - Gravity = ConstantGravity(; g=9.81), - ), - # # Name = "gabbro - oceanic lithosphere", - # SetMaterialParams(; - # Phase = 2, - # Density = PT_Density(; ρ0=3e3, α = α, T0 = 273), - # CompositeRheology = CompositeRheology( - # ( - # disl_gabro, - # diff_gabro, - # DruckerPrager_regularised(; C = C, ϕ=ϕ_gabbro, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity - # ) - # ), - # RadioactiveHeat = ConstantRadioactiveHeat(0.022), - # # Elasticity = el_upper_crust, - # Gravity = ConstantGravity(; g=9.81), - # ), - # # Name = "lower slab", - # SetMaterialParams(; - # Phase = 3, - # Density = ConstantDensity(; ρ=3.28e3), - # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), - # # Elasticity = el_upper_crust, - # ), - # Name = "wet qtz - upper continental crust", - SetMaterialParams(; - Phase = 2, - Density = PT_Density(; ρ0=2.75e3, α = α, T0 = 273), - CompositeRheology = CompositeRheology( - ( - disl_wet_quartzite, - diff_wet_quartzite, - DruckerPrager_regularised(; C = C, ϕ=ϕ_wet_quartzite, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(2), - # Elasticity = el_upper_crust, - ), - # Name = "plagioclase - lower continental crust", - SetMaterialParams(; - Phase = 3, - Density = PT_Density(; ρ0=3e3, α = α, T0 = 273), - CompositeRheology = CompositeRheology( - ( - disl_plagioclase, - diff_plagioclase, - DruckerPrager_regularised(; C = C, ϕ=ϕ_plagioclase, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(0.2), - # Elasticity = el_upper_crust, - ), - # # Name = "lithosphere", - # SetMaterialParams(; - # Phase = 6, - # Density = PT_Density(; ρ0=3.3e3, α = α, T0 = 273), - # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), - # # Elasticity = el_upper_crust, - # ), - # Name = "wet ol - weak zone", - SetMaterialParams(; - Phase = 4, - Density = PT_Density(; ρ0=3.2e3, α = α, T0 = 273), - RadioactiveHeat = ConstantRadioactiveHeat(0.022), - # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), - CompositeRheology = CompositeRheology( - ( - disl_wet_olivine, - diff_wet_olivine, - DruckerPrager_regularised(; C = C, ϕ=ϕ_wet_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity - ) - ), - # Elasticity = el_upper_crust, - ), - ) -end - -function init_phases!(phases, phase_grid, particles, xvi) - ni = size(phases) - @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) -end - -@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} - - ni = size(phases) - - for ip in JustRelax.cellaxes(phases) - # quick escape - @cell(index[ip, I...]) == 0 && continue - - pᵢ = ntuple(Val(N)) do i - @cell pcoords[i][ip, I...] - end - - d = Inf # distance to the nearest particle - particle_phase = -1 - for offi in 0:1, offj in 0:1 - ii = I[1] + offi - jj = I[2] + offj - - !(ii ≤ ni[1]) && continue - !(jj ≤ ni[2]) && continue - - xvᵢ = ( - xvi[1][ii], - xvi[2][jj], - ) - d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) - if d_ijk < d - d = d_ijk - particle_phase = phase_grid[ii, jj] - end - end - @cell phases[ip, I...] = Float64(particle_phase) - end - - return nothing -end \ No newline at end of file diff --git a/subduction/Subduction_rheology2D.jl b/subduction/Subduction_rheology2D.jl index 76f2c59a..9f7a0053 100644 --- a/subduction/Subduction_rheology2D.jl +++ b/subduction/Subduction_rheology2D.jl @@ -1,32 +1,139 @@ # from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 + + function init_rheologies() + disl_dry_olivine = DislocationCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) + disl_wet_olivine = DislocationCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) + disl_wet_quartzite = DislocationCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) + disl_plagioclase = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + disl_gabbro = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + + diff_dry_olivine = DiffusionCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) + diff_wet_olivine = DiffusionCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) + diff_wet_quartzite = DiffusionCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) + diff_plagioclase = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + diff_gabbro = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + + ϕ_dry_olivine = asind(0.6) + ϕ_wet_olivine = asind(0.1) + ϕ_wet_quartzite = asind(0.3) + ϕ_plagioclase = asind(0.3) + + # common physical properties + α = 3e-5 # 1 / K + Cp = 1000 # J / kg K + C = 3e6 # Pa + η_reg = 1e18 + # Define rheolgy struct rheology = ( - # Name = "crust", + # Name = "dry ol - lithospheric mantle", SetMaterialParams(; Phase = 1, - Density = ConstantDensity(; ρ=3.28e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e20), ) ), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + DruckerPrager_regularised(; C = C, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(0.022), # Elasticity = el_upper_crust, Gravity = ConstantGravity(; g=9.81), ), - # Name = "slab", + # # Name = "gabbro - oceanic lithosphere", + # SetMaterialParams(; + # Phase = 2, + # Density = PT_Density(; ρ0=3e3, α = α, T0 = 273), + # CompositeRheology = CompositeRheology( + # ( + # disl_gabro, + # diff_gabro, + # DruckerPrager_regularised(; C = C, ϕ=ϕ_gabbro, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + # ) + # ), + # RadioactiveHeat = ConstantRadioactiveHeat(0.022), + # # Elasticity = el_upper_crust, + # Gravity = ConstantGravity(; g=9.81), + # ), + # # Name = "lower slab", + # SetMaterialParams(; + # Phase = 3, + # Density = ConstantDensity(; ρ=3.28e3), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # # Elasticity = el_upper_crust, + # ), + # Name = "wet qtz - upper continental crust", SetMaterialParams(; Phase = 2, - Density = ConstantDensity(; ρ=3.28e3), - # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), - CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + Density = PT_Density(; ρ0=2.75e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e20), ) ), + CompositeRheology = CompositeRheology( + ( + disl_wet_quartzite, + diff_wet_quartzite, + DruckerPrager_regularised(; C = C, ϕ=ϕ_wet_quartzite, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2), # Elasticity = el_upper_crust, - Gravity = ConstantGravity(; g=9.81), ), - # Name = "mantle", + # Name = "plagioclase - lower continental crust", SetMaterialParams(; Phase = 3, - Density = ConstantDensity(; ρ=3.2e3), - CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + Density = PT_Density(; ρ0=3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e20), ) ), + CompositeRheology = CompositeRheology( + ( + disl_plagioclase, + diff_plagioclase, + DruckerPrager_regularised(; C = C, ϕ=ϕ_plagioclase, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(0.2), + # Elasticity = el_upper_crust, + ), + # # Name = "lithosphere", + # SetMaterialParams(; + # Phase = 6, + # Density = PT_Density(; ρ0=3.3e3, α = α, T0 = 273), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # # Elasticity = el_upper_crust, + # ), + # Name = "wet ol - weak zone", + SetMaterialParams(; + Phase = 4, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + RadioactiveHeat = ConstantRadioactiveHeat(0.022), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + CompositeRheology = CompositeRheology( + ( + disl_wet_olivine, + diff_wet_olivine, + DruckerPrager_regularised(; C = 5e6, ϕ=ϕ_wet_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), # Elasticity = el_upper_crust, - Gravity = ConstantGravity(; g=9.81), + ), + # Name = "StickyAir", + SetMaterialParams(; + Phase = 5, + Density = ConstantDensity(; ρ=1e1), # water density + HeatCapacity = ConstantHeatCapacity(; Cp=3e3), + RadioactiveHeat = ConstantRadioactiveHeat(0.0), + Conductivity = ConstantConductivity(; k=1.0), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e19),)), ), ) end @@ -42,35 +149,33 @@ end for ip in JustRelax.cellaxes(phases) # quick escape - JustRelax.@cell(index[ip, I...]) == 0 && continue + @cell(index[ip, I...]) == 0 && continue pᵢ = ntuple(Val(N)) do i - JustRelax.@cell pcoords[i][ip, I...] + @cell pcoords[i][ip, I...] end d = Inf # distance to the nearest particle particle_phase = -1 - for offi in 0:1, offj in 0:1, offk in 0:1 - ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk + for offi in 0:1, offj in 0:1 + ii = I[1] + offi + jj = I[2] + offj !(ii ≤ ni[1]) && continue !(jj ≤ ni[2]) && continue - !(kk ≤ ni[3]) && continue xvᵢ = ( xvi[1][ii], xvi[2][jj], - xvi[3][kk], ) - # @show xvᵢ ii jj kk d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) if d_ijk < d d = d_ijk - particle_phase = phase_grid[ii, jj, kk] + particle_phase = phase_grid[ii, jj] end end - JustRelax.@cell phases[ip, I...] = Float64(particle_phase) + @cell phases[ip, I...] = Float64(particle_phase) end return nothing -end \ No newline at end of file +end diff --git a/subduction/Subduction_rheology3D.jl b/subduction/Subduction_rheology3D.jl new file mode 100644 index 00000000..76f2c59a --- /dev/null +++ b/subduction/Subduction_rheology3D.jl @@ -0,0 +1,76 @@ +# from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 + +function init_rheologies() + # Define rheolgy struct + rheology = ( + # Name = "crust", + SetMaterialParams(; + Phase = 1, + Density = ConstantDensity(; ρ=3.28e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "slab", + SetMaterialParams(; + Phase = 2, + Density = ConstantDensity(; ρ=3.28e3), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + CompositeRheology = CompositeRheology( (LinearViscous(η = 2e23), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "mantle", + SetMaterialParams(; + Phase = 3, + Density = ConstantDensity(; ρ=3.2e3), + CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + JustRelax.@cell(index[ip, I...]) == 0 && continue + + pᵢ = ntuple(Val(N)) do i + JustRelax.@cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1, offk in 0:1 + ii, jj, kk = I[1] + offi, I[2] + offj, I[3] + offk + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + !(kk ≤ ni[3]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + xvi[3][kk], + ) + # @show xvᵢ ii jj kk + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj, kk] + end + end + JustRelax.@cell phases[ip, I...] = Float64(particle_phase) + end + + return nothing +end \ No newline at end of file From e18777010f83a8a3e6b39885f5f6cbc9effc26fd Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 9 Apr 2024 12:35:35 +0200 Subject: [PATCH 27/60] up --- subduction/Subduction_rheology2D.jl | 4 ++-- test/test_boundary_conditions2D.jl | 33 +++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/subduction/Subduction_rheology2D.jl b/subduction/Subduction_rheology2D.jl index 9f7a0053..ca72d253 100644 --- a/subduction/Subduction_rheology2D.jl +++ b/subduction/Subduction_rheology2D.jl @@ -129,11 +129,11 @@ function init_rheologies() # Name = "StickyAir", SetMaterialParams(; Phase = 5, - Density = ConstantDensity(; ρ=1e1), # water density + Density = ConstantDensity(; ρ=1e3), # water density HeatCapacity = ConstantHeatCapacity(; Cp=3e3), RadioactiveHeat = ConstantRadioactiveHeat(0.0), Conductivity = ConstantConductivity(; k=1.0), - CompositeRheology = CompositeRheology((LinearViscous(; η=1e19),)), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e21),)), ), ) end diff --git a/test/test_boundary_conditions2D.jl b/test/test_boundary_conditions2D.jl index fe662aeb..b3bba854 100644 --- a/test/test_boundary_conditions2D.jl +++ b/test/test_boundary_conditions2D.jl @@ -14,10 +14,10 @@ environment!(model) ) flow_bcs!(bcs, Vx, Vy) - @test @views Vx[:, 1] == Vx[:, 2] - @test @views Vx[:, end] == Vx[:, end - 1] - @test @views Vy[1, :] == Vy[2, :] - @test @views Vy[end, :] == Vy[end - 1, :] + @test @views Vx[:, 1] == Vx[:, 2] + @test @views Vx[:, end] == Vx[:, end - 1] + @test @views Vy[1, :] == Vy[2, :] + @test @views Vy[end, :] == Vy[end - 1, :] # no-slip Vx, Vy = PTArray(rand(n + 1, n + 2)), PTArray(rand(n + 2, n + 1)) @@ -47,10 +47,10 @@ environment!(model) ) flow_bcs!(stokes, flow_bcs) - @test @views stokes.V.Vx[ :, 1] == stokes.V.Vx[ :, 2] - @test @views stokes.V.Vx[ :, end] == stokes.V.Vx[ :, end - 1] - @test @views stokes.V.Vy[ 1, :] == stokes.V.Vy[ 2, :] - @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] + @test @views stokes.V.Vx[ :, 1] == stokes.V.Vx[ :, 2] + @test @views stokes.V.Vx[ :, end] == stokes.V.Vx[ :, end - 1] + @test @views stokes.V.Vy[ 1, :] == stokes.V.Vy[ 2, :] + @test @views stokes.V.Vy[end, :] == stokes.V.Vy[end - 1, :] # no-slip flow_bcs = FlowBoundaryConditions(; @@ -59,14 +59,15 @@ environment!(model) ) flow_bcs!(stokes, flow_bcs) - @test sum(!iszero(stokes.V.Vx[1 , i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(stokes.V.Vx[end, i]) for i in axes(Vx,2)) == 0 - @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 - @test @views stokes.V.Vy[1 , :] == -stokes.V.Vy[2 , :] - @test @views stokes.V.Vy[end, :] == -stokes.V.Vy[end - 1, :] - @test @views stokes.V.Vx[: , 1] == -stokes.V.Vx[: , 2] - @test @views stokes.V.Vx[: , end] == -stokes.V.Vx[: , end - 1] + @test sum(!iszero(stokes.V.Vx[1 , i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(stokes.V.Vx[end, i]) for i in axes(Vx,2)) == 0 + @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test sum(!iszero(stokes.V.Vy[i, 1]) for i in axes(Vy,1)) == 0 + @test @views stokes.V.Vy[1 , :] == -stokes.V.Vy[2 , :] + @test @views stokes.V.Vy[end, :] == -stokes.V.Vy[end - 1, :] + @test @views stokes.V.Vx[: , 1] == -stokes.V.Vx[: , 2] + @test @views stokes.V.Vx[: , end] == -stokes.V.Vx[: , end - 1] + end end @testset "Temperature boundary conditions 2D" begin From 1538b66e2491db1c6d89d7dcaad15158498fa4f6 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 9 Apr 2024 14:41:28 +0200 Subject: [PATCH 28/60] no sticky air versions --- subduction/GMG_setup2D_noair.jl | 87 ++++++ subduction/Subduction2D_noair.jl | 353 ++++++++++++++++++++++ subduction/Subduction_rheology2D_noair.jl | 172 +++++++++++ 3 files changed, 612 insertions(+) create mode 100644 subduction/GMG_setup2D_noair.jl create mode 100644 subduction/Subduction2D_noair.jl create mode 100644 subduction/Subduction_rheology2D_noair.jl diff --git a/subduction/GMG_setup2D_noair.jl b/subduction/GMG_setup2D_noair.jl new file mode 100644 index 00000000..c80dcba6 --- /dev/null +++ b/subduction/GMG_setup2D_noair.jl @@ -0,0 +1,87 @@ + +#= +# Creating 2D numerical model setups + +### Aim +The aim of this tutorial is to show you how to create 2D numerical model setups that can be used as initial setups for other codes. + +=# + + +#= +### 2D Subduction setup + +Lets start with creating a 2D model setup in cartesian coordinates, which uses the `CartData` data structure +=# +using GeophysicalModelGenerator + +function GMG_subduction_2D(nx, ny) + # Our starting basis is the example above with ridge and overriding slab + nx, nz = nx, ny + x = range(-1000, 1000, nx); + z = range(-660, 0, nz); + Grid2D = CartData(xyz_grid(x,0,z)) + Phases = zeros(Int64, nx, 1, nz); + Temp = fill(1350.0, nx, 1, nz); + air_thickness = 20.0 + lith = LithosphericPhases(Layers=[15 20 55], Phases=[3 4 5], Tlab=1250) + # mantle = LithosphericPhases(Phases=[1]) + + # add_box!(Phases, Temp, Grid2D; xlim=(-1000, 1000), zlim=(-600.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + # Phases .= 0 + + # Lets start with defining the horizontal part of the overriding plate. + # Note that we define this twice with different thickness to deal with the bending subduction area: + add_box!(Phases, Temp, Grid2D; xlim=(200,1000), zlim=(-150.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + add_box!(Phases, Temp, Grid2D; xlim=(0,200), zlim=(-50.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + + # The horizontal part of the oceanic plate is as before: + v_spread_cm_yr = 3 #spreading velocity + lith = LithosphericPhases(Layers=[15 55], Phases=[1 2], Tlab=1250) + add_box!(Phases, Temp, Grid2D; xlim=(-1000,0.0), zlim=(-150.0, 0.0), phase = lith, T=SpreadingRateTemp(SpreadingVel=v_spread_cm_yr)); + + # Yet, now we add a trench as well. The starting thermal age at the trench is that of the horizontal part of the oceanic plate: + AgeTrench_Myrs = 1000e3/(v_spread_cm_yr/1e2)/1e6 #plate age @ trench + + # We want to add a smooth transition from a halfspace cooling 1D thermal profile to a slab that is heated by the surrounding mantle below a decoupling depth `d_decoupling`. + T_slab = LinearWeightedTemperature( + F1=HalfspaceCoolingTemp(Age=AgeTrench_Myrs), + F2=McKenzie_subducting_slab(Tsurface=0,v_cm_yr=v_spread_cm_yr, Adiabat = 0.0) + ) + + # in this case, we have a more reasonable slab thickness: + trench = Trench(Start=(0.0, -100.0), End=(0.0, 100.0), Thickness=100.0, θ_max=30.0, Length=300, Lb=200, + WeakzoneThickness=15, WeakzonePhase=6, d_decoupling=125); + add_slab!(Phases, Temp, Grid2D, trench, phase = lith, T=T_slab); + + # Lithosphere-asthenosphere boundary: + ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); + Phases[ind] .= 0; + + surf = Grid2D.z.val .> 0.0 + Temp[surf] .= 0.0 + # Phases[surf] .= 7 + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + + li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 + origin = (x[1], z[1]) .* 1e3 + + ph = Phases[:,1,:] .+ 1; + ph2 = ph .== 2 + ph3 = ph .== 3 + ph4 = ph .== 4 + ph5 = ph .== 5 + ph6 = ph .== 6 + ph7 = ph .== 7 + # ph8 = ph .== 8 + ph[ph2] .= 1 + ph[ph3] .= 1 + ph[ph4] .= 2 + ph[ph5] .= 3 + ph[ph6] .= 1 + ph[ph7] .= 4 + ph[ph8] .= 5 + + return li, origin, ph, Temp[:,1,:].+273 +end diff --git a/subduction/Subduction2D_noair.jl b/subduction/Subduction2D_noair.jl new file mode 100644 index 00000000..fe4ab939 --- /dev/null +++ b/subduction/Subduction2D_noair.jl @@ -0,0 +1,353 @@ +# using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +@init_parallel_stencil(Threads, Float64, 2) +# @init_parallel_stencil(CUDA, Float64, 2) + +using JustPIC +using JustPIC._2D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("Subduction_rheology2D_noair.jl") +include("GMG_setup2D_noair.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[2] +macro all_k(A) + esc(:($A[$idx_k])) +end + +function copyinn_x!(A, B) + @parallel function f_x(A, B) + @all(A) = @inn_x(B) + return nothing + end + + @parallel f_x(A, B) +end + + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) + + # Physical domain ------------------------------------ + ni = nx, ny # number of cells + di = @. li / ni # grid steps + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies() + dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi, di, ni + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pPhases, pT = init_cell_arrays(particles, Val(2)) + particle_args = (pPhases, pT) + + # Assign particles phases anomaly + phases_device = PTArray(phases_GMG) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.99 / √2.1) + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + thermal = ThermalArrays(ni) + @views thermal.T[2:end-1, :] .= PTArray(T_GMG) + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = false, bot = false), + ) + thermal_bcs!(thermal, thermal_bc) + @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(2)) + for _ in 1:2 + compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) + end + # Rheology + η = @ones(ni...) + η_vep = similar(η) + args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + + # PT coefficients for thermal diffusion + pt_thermal = PTThermalCoeffs( + rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-2 / √3 + ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true), + free_surface = false, + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(@velocity(stokes)...) + + # # Plot initial T and η profiles + # let + # Yv = [y for x in xvi[1], y in xvi[2]][:] + # Y = [y for x in xci[1], y in xci[2]][:] + # fig = Figure(size = (1200, 900)) + # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + # scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + # scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # # scatter!(ax2, Array(ρg[2][:]), Y./1e3) + # ylims!(ax1, minimum(xvi[2])./1e3, 0) + # ylims!(ax2, minimum(xvi[2])./1e3, 0) + # hideydecorations!(ax2) + # # save(joinpath(figdir, "initial_profile.png"), fig) + # fig + # end + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + end + + T_buffer = @zeros(ni.+1) + Told_buffer = similar(T_buffer) + dt₀ = similar(stokes.P) + for (dst, src) in zip((T_buffer, Told_buffer), (thermal.T, thermal.Told)) + copyinn_x!(dst, src) + end + grid2particle!(pT, xvi, T_buffer, particles) + + # Time loop + t, it = 0.0, 0 + while it < 1000 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # interpolate fields from particle to grid vertices + particle2grid!(T_buffer, pT, xvi, particles) + @views T_buffer[:, end] .= 273.0 + @views T_buffer[:, 1] .= 1623.0 + @views thermal.T[2:end-1, :] .= T_buffer + thermal_bcs!(thermal, thermal_bc) + temperature2center!(thermal) + + # interpolate fields from particle to grid vertices + # particle2grid!(thermal.T, pT, xvi, particles) + # temperature2center!(thermal) + # Update buoyancy and viscosity - + args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + compute_ρg!(ρg[2], phase_ratios, rheology, args) + + # Stokes solver ---------------- + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + dt, + igg; + iterMax = 50e3, + nout = 2e3, + viscosity_cutoff = (1e18, 1e24), + free_surface = false, + # viscosity_relaxation = 1e-5 + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") + @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + dt = compute_dt(stokes, di) + # ------------------------------ + + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology, + args, + dt, + di; + igg = igg, + phase = phase_ratios, + iterMax = 10e3, + nout = 1e2, + verbose = true, + ) + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection_RK!(particles, @velocity(stokes), grid_vxi..., dt, 2 / 3) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject = check_injection(particles) + inject && inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer,), xvi) + # update phase ratios + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 5) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) + data_v = (; + T = Array(thermal.T), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + ) + data_c = (; + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(η), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + # Make particles plottable + p = particles.coords + ppx, ppy = p + pxv = ppx.data[:]./1e3 + pyv = ppy.data[:]./1e3 + clr = pPhases.data[:] + # clr = pT.data[:] + idxv = particles.index.data[:]; + + # Make Makie figure + ar = 3 + fig = Figure(size = (1200, 900), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "T [K] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "Vy [m/s]") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) + # Plot particles phase + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η_vep)) , colormap=:batlow) + hidexdecorations!(ax1) + hidexdecorations!(ax2) + hidexdecorations!(ax3) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end + +## END OF MAIN SCRIPT ---------------------------------------------------------------- +do_vtk = true # set to true to generate VTK files for ParaView +figdir = "Subduction2D" +# nx, ny = 512, 256 +# nx, ny = 512, 128 +nx, ny = 256, 64 +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) +else + igg +end + +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); + diff --git a/subduction/Subduction_rheology2D_noair.jl b/subduction/Subduction_rheology2D_noair.jl new file mode 100644 index 00000000..c9ef68c9 --- /dev/null +++ b/subduction/Subduction_rheology2D_noair.jl @@ -0,0 +1,172 @@ +# from "Fingerprinting secondary mantle plumes", Cloetingh et al. 2022 + + + +function init_rheologies() + disl_dry_olivine = DislocationCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) + disl_wet_olivine = DislocationCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) + disl_wet_quartzite = DislocationCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) + disl_plagioclase = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + disl_gabbro = DislocationCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + + diff_dry_olivine = DiffusionCreep(A=2.5e-17 , n=3.5, E=532e3, V=0e0 , r=0.0, R=8.3145) + diff_wet_olivine = DiffusionCreep(A=9e-20 , n=3.5, E=480e3, V=11e-6, r=0.0, R=8.3145) + diff_wet_quartzite = DiffusionCreep(A=1.97e-17, n=2.3, E=164e3, V=0e0 , r=0.0, R=8.3145) + diff_plagioclase = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + diff_gabbro = DiffusionCreep(A=4.8e-22 , n=3.2, E=238e3, V=0e0 , r=0.0, R=8.3145) + + ϕ_dry_olivine = asind(0.6) + ϕ_wet_olivine = asind(0.1) + ϕ_wet_quartzite = asind(0.3) + ϕ_plagioclase = asind(0.3) + + # common physical properties + α = 3e-5 # 1 / K + Cp = 1000 # J / kg K + C = 3e6 # Pa + η_reg = 1e18 + + # Define rheolgy struct + rheology = ( + # Name = "dry ol - lithospheric mantle", + SetMaterialParams(; + Phase = 1, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e20), ) ), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + DruckerPrager_regularised(; C = C, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(0.022), + # Elasticity = el_upper_crust, + Gravity = ConstantGravity(; g=9.81), + ), + # # Name = "gabbro - oceanic lithosphere", + # SetMaterialParams(; + # Phase = 2, + # Density = PT_Density(; ρ0=3e3, α = α, T0 = 273), + # CompositeRheology = CompositeRheology( + # ( + # disl_gabro, + # diff_gabro, + # DruckerPrager_regularised(; C = C, ϕ=ϕ_gabbro, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + # ) + # ), + # RadioactiveHeat = ConstantRadioactiveHeat(0.022), + # # Elasticity = el_upper_crust, + # Gravity = ConstantGravity(; g=9.81), + # ), + # # Name = "lower slab", + # SetMaterialParams(; + # Phase = 3, + # Density = ConstantDensity(; ρ=3.28e3), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # # Elasticity = el_upper_crust, + # ), + # Name = "wet qtz - upper continental crust", + SetMaterialParams(; + Phase = 2, + Density = PT_Density(; ρ0=2.75e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e20), ) ), + CompositeRheology = CompositeRheology( + ( + disl_wet_quartzite, + diff_wet_quartzite, + DruckerPrager_regularised(; C = C, ϕ=ϕ_wet_quartzite, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2), + # Elasticity = el_upper_crust, + ), + # Name = "plagioclase - lower continental crust", + SetMaterialParams(; + Phase = 3, + Density = PT_Density(; ρ0=3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e20), ) ), + CompositeRheology = CompositeRheology( + ( + disl_plagioclase, + diff_plagioclase, + DruckerPrager_regularised(; C = C, ϕ=ϕ_plagioclase, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(0.2), + # Elasticity = el_upper_crust, + ), + # # Name = "lithosphere", + # SetMaterialParams(; + # Phase = 6, + # Density = PT_Density(; ρ0=3.3e3, α = α, T0 = 273), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e23), ) ), + # # Elasticity = el_upper_crust, + # ), + # Name = "wet ol - weak zone", + SetMaterialParams(; + Phase = 4, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + RadioactiveHeat = ConstantRadioactiveHeat(0.022), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + # CompositeRheology = CompositeRheology( (LinearViscous(η = 1e21), ) ), + CompositeRheology = CompositeRheology( + ( + disl_wet_olivine, + diff_wet_olivine, + DruckerPrager_regularised(; C = 5e6, ϕ=ϕ_wet_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + # Elasticity = el_upper_crust, + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + @cell(index[ip, I...]) == 0 && continue + + pᵢ = ntuple(Val(N)) do i + @cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1 + ii = I[1] + offi + jj = I[2] + offj + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + ) + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj] + end + end + @cell phases[ip, I...] = Float64(particle_phase) + end + + return nothing +end From 103e20ff28d672a7671eb5940d65f9940100542d Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 9 Apr 2024 16:39:13 +0200 Subject: [PATCH 29/60] remove dead argurment --- src/rheology/StressUpdate.jl | 1 - src/stokes/Stokes2D.jl | 2 -- src/stokes/Stokes3D.jl | 2 -- src/stokes/StressKernels.jl | 6 ++---- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/rheology/StressUpdate.jl b/src/rheology/StressUpdate.jl index dad50c08..81be9edb 100644 --- a/src/rheology/StressUpdate.jl +++ b/src/rheology/StressUpdate.jl @@ -1,7 +1,6 @@ # inner kernel to compute the plastic stress update within Pseudo-Transient stress continuation function _compute_τ_nonlinear!( τ::NTuple{N1,T}, - τII, τ_old::NTuple{N1,T}, ε::NTuple{N1,T}, ε_pl::NTuple{N1,T}, diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index e2d99029..3be0b181 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -393,7 +393,6 @@ function JustRelax.solve!( @parallel (@idx ni) compute_τ_nonlinear!( @tensor_center(stokes.τ), - stokes.τ.II, @tensor(stokes.τ_o), @strain(stokes), @tensor_center(stokes.ε_pl), @@ -605,7 +604,6 @@ function JustRelax.solve!( @parallel (@idx ni) compute_τ_nonlinear!( @tensor_center(stokes.τ), - stokes.τ.II, @tensor_center(stokes.τ_o), @strain(stokes), @tensor_center(stokes.ε_pl), diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index 83f654fc..1a436268 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -282,7 +282,6 @@ function JustRelax.solve!( @parallel (@idx ni) compute_τ_nonlinear!( @tensor_center(stokes.τ), - stokes.τ.II, @tensor(stokes.τ_o), @strain(stokes), @tensor_center(stokes.ε_pl), @@ -473,7 +472,6 @@ function JustRelax.solve!( # if !cte_viscosity @parallel (@idx ni) compute_τ_nonlinear!( @tensor_center(stokes.τ), - stokes.τ.II, @tensor_center(stokes.τ_o), @strain(stokes), @tensor_center(stokes.ε_pl), diff --git a/src/stokes/StressKernels.jl b/src/stokes/StressKernels.jl index c44ddd28..a08aa013 100644 --- a/src/stokes/StressKernels.jl +++ b/src/stokes/StressKernels.jl @@ -376,7 +376,6 @@ end @parallel_indices (I...) function compute_τ_nonlinear!( τ, # @ centers - τII, # @ centers τ_old, # @ centers ε, # @ vertices ε_pl, # @ centers @@ -405,7 +404,7 @@ end plastic_parameters = (; is_pl, C, sinϕ, cosϕ, η_reg, volume) _compute_τ_nonlinear!( - τ, τII, τ_old, ε, ε_pl, P, ηij, η_vep, λ, dτ_r, _Gdt, plastic_parameters, I... + τ, τ_old, ε, ε_pl, P, ηij, η_vep, λ, dτ_r, _Gdt, plastic_parameters, I... ) # augmented pressure with plastic volumetric strain over pressure @@ -417,7 +416,6 @@ end # multi phase visco-elasto-plastic flow, where phases are defined in the cell center @parallel_indices (I...) function compute_τ_nonlinear!( τ, # @ centers - τII, # @ centers τ_old, # @ centers ε, # @ vertices ε_pl, # @ centers @@ -447,7 +445,7 @@ end plastic_parameters = (; is_pl, C, sinϕ, cosϕ, η_reg, volume) _compute_τ_nonlinear!( - τ, τII, τ_old, ε, ε_pl, P, ηij, η_vep, λ, dτ_r, _Gdt, plastic_parameters, I... + τ, τ_old, ε, ε_pl, P, ηij, η_vep, λ, dτ_r, _Gdt, plastic_parameters, I... ) # augmented pressure with plastic volumetric strain over pressure @inbounds θ[I...] = P[I...] + (isinf(K) ? 0.0 : K * dt * λ[I...] * sinψ) From cc9221528072d4eb3d8b551e8b71a9697213c831 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Tue, 23 Apr 2024 17:09:58 +0200 Subject: [PATCH 30/60] LAMEM 2D subdcuction miniapp --- subduction/LAMEM2D.jl | 358 +++++++++++++++++++++++++++++++++++ subduction/LAMEM_rheology.jl | 161 ++++++++++++++++ subduction/LAMEM_setup2D.jl | 124 ++++++++++++ 3 files changed, 643 insertions(+) create mode 100644 subduction/LAMEM2D.jl create mode 100644 subduction/LAMEM_rheology.jl create mode 100644 subduction/LAMEM_setup2D.jl diff --git a/subduction/LAMEM2D.jl b/subduction/LAMEM2D.jl new file mode 100644 index 00000000..afcde93f --- /dev/null +++ b/subduction/LAMEM2D.jl @@ -0,0 +1,358 @@ +using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +# @init_parallel_stencil(Threads, Float64, 2) +@init_parallel_stencil(CUDA, Float64, 2) + +using JustPIC +using JustPIC._2D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +# model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("LAMEM_rheology.jl") +include("LAMEM_setup2D.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[2] +macro all_k(A) + esc(:($A[$idx_k])) +end + +function copyinn_x!(A, B) + @parallel function f_x(A, B) + @all(A) = @inn_x(B) + return nothing + end + + @parallel f_x(A, B) +end + + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) + + # Physical domain ------------------------------------ + ni = nx, ny # number of cells + di = @. li / ni # grid steps + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies() + dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi, di, ni + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pPhases, pT = init_cell_arrays(particles, Val(2)) + particle_args = (pPhases, pT) + + # Assign particles phases anomaly + phases_device = PTArray(phases_GMG) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.99 / √2.1) + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + Ttop = 20 + Tbot = 1565.0 + thermal = ThermalArrays(ni) + @views thermal.T[2:end-1, :] .= PTArray(T_GMG) + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = false, bot = false), + ) + thermal_bcs!(thermal, thermal_bc) + @views thermal.T[:, end] .= Ttop + @views thermal.T[:, 1] .= Tbot + @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(2)) + for _ in 1:2 + compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) + end + + # Rheology + η = @ones(ni...) + η_vep = similar(η) + args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + + # PT coefficients for thermal diffusion + pt_thermal = PTThermalCoeffs( + rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-2 / √3 + ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true), + free_surface = false, + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(@velocity(stokes)...) + + # # Plot initial T and η profiles + # let + # Yv = [y for x in xvi[1], y in xvi[2]][:] + # Y = [y for x in xci[1], y in xci[2]][:] + # fig = Figure(size = (1200, 900)) + # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + # scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + # scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # # scatter!(ax2, Array(ρg[2][:]), Y./1e3) + # ylims!(ax1, minimum(xvi[2])./1e3, 0) + # ylims!(ax2, minimum(xvi[2])./1e3, 0) + # hideydecorations!(ax2) + # # save(joinpath(figdir, "initial_profile.png"), fig) + # fig + # end + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + end + + T_buffer = @zeros(ni.+1) + Told_buffer = similar(T_buffer) + dt₀ = similar(stokes.P) + for (dst, src) in zip((T_buffer, Told_buffer), (thermal.T, thermal.Told)) + copyinn_x!(dst, src) + end + grid2particle!(pT, xvi, T_buffer, particles) + + # Time loop + t, it = 0.0, 0 + + while it < 1000 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # interpolate fields from particle to grid vertices + particle2grid!(T_buffer, pT, xvi, particles) + @views T_buffer[:, end] .= Ttop + @views T_buffer[:, 1] .= Tbot + @views thermal.T[2:end-1, :] .= T_buffer + thermal_bcs!(thermal, thermal_bc) + temperature2center!(thermal) + + # interpolate fields from particle to grid vertices + # particle2grid!(thermal.T, pT, xvi, particles) + # temperature2center!(thermal) + # Update buoyancy and viscosity - + args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + compute_ρg!(ρg[2], phase_ratios, rheology, args) + + # Stokes solver ---------------- + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + dt, + igg; + iterMax = 150e3, + nout = 2e3, + viscosity_cutoff = (1e18, 1e24), + free_surface = false, + # viscosity_relaxation = 1e-5 + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") + @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + dt = compute_dt(stokes, di) + # ------------------------------ + + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology, + args, + dt, + di; + igg = igg, + phase = phase_ratios, + iterMax = 10e3, + nout = 1e2, + verbose = true, + ) + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer,), xvi) + # update phase ratios + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 5) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) + data_v = (; + T = Array(thermal.T), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + ) + data_c = (; + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(η), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + # Make particles plottable + p = particles.coords + ppx, ppy = p + pxv = ppx.data[:]./1e3 + pyv = ppy.data[:]./1e3 + clr = pPhases.data[:] + # clr = pT.data[:] + idxv = particles.index.data[:]; + + # Make Makie figure + ar = 3 + fig = Figure(size = (1200, 900), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "T [K] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "Vy [m/s]") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) + # Plot particles phase + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η_vep)) , colormap=:batlow) + hidexdecorations!(ax1) + hidexdecorations!(ax2) + hidexdecorations!(ax3) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end + +## END OF MAIN SCRIPT ---------------------------------------------------------------- +do_vtk = true # set to true to generate VTK files for ParaView +figdir = "Subduction_LAMEM_2D" +# nx, ny = 512, 256 +# nx, ny = 512, 128 +nx, ny = 384, 64 +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) +else + igg +end + +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); + diff --git a/subduction/LAMEM_rheology.jl b/subduction/LAMEM_rheology.jl new file mode 100644 index 00000000..d3d2c22f --- /dev/null +++ b/subduction/LAMEM_rheology.jl @@ -0,0 +1,161 @@ +using GeoParams.Dislocation +using GeoParams.Diffusion + +function init_rheologies() + disl_dry_olivine = SetDislocationCreep(Dislocation.dry_olivine_Hirth_2003; V = 14.5e-6) + disl_oceanic_crust = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) + # disl_oceanic_litho = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) + disl_cont_crust = SetDislocationCreep(Dislocation.wet_quartzite_Kirby_1983) + + diff_dry_olivine = SetDiffusionCreep(Diffusion.dry_olivine_Hirth_2003; V = 14.5e-6) + + ϕ_dry_olivine = sind(20) + C_dry_olivine = 30e6 + + ϕ_oceanic_crust = sind(0) + C_oceanic_crust = 5e6 + + ϕ_oceanic_litho = sind(0) + C_oceanic_litho = 5e6 + + ϕ_cont_crust = sind(20) + C_cont_crust = 30e6 + + soft_C = LinearSoftening((C_oceanic_litho*0.95, C_oceanic_litho), (0.1, 0.5)) + + # common physical properties + α = 3e-5 # 1 / K + Cp = 1000 # J / kg K + # C = 3e6 # Pa + η_reg = 1e18 + + + # Define rheolgy struct + rheology = ( + # Name = "dry olivine - Hirth_Kohlstedt_2003", + SetMaterialParams(; + Phase = 1, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "oceanic crust", + SetMaterialParams(; + Phase = 2, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + CompositeRheology = CompositeRheology( + ( + disl_oceanic_crust, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + # Name = "oceanic lithosphere", + SetMaterialParams(; + Phase = 3, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + CompositeRheology = CompositeRheology( + ( + disl_oceanic_crust, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_oceanic_litho, ϕ = ϕ_oceanic_litho, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + # Name = "continental crust", + SetMaterialParams(; + Phase = 4, + Density = PT_Density(; ρ0=2.7e3, α = α, β = 0e0, T0 = 273), + RadioactiveHeat = ConstantRadioactiveHeat(5.3571e-10), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + CompositeRheology = CompositeRheology( + ( + disl_cont_crust, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + # Name = "continental lithosphere", + SetMaterialParams(; + Phase = 5, + Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + @cell(index[ip, I...]) == 0 && continue + + pᵢ = ntuple(Val(N)) do i + @cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1 + ii = I[1] + offi + jj = I[2] + offj + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + ) + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj] + end + end + @cell phases[ip, I...] = Float64(particle_phase) + end + + return nothing +end diff --git a/subduction/LAMEM_setup2D.jl b/subduction/LAMEM_setup2D.jl new file mode 100644 index 00000000..5a3eeda6 --- /dev/null +++ b/subduction/LAMEM_setup2D.jl @@ -0,0 +1,124 @@ +using GeophysicalModelGenerator + + +function GMG_subduction_2D(nx, ny) + # Our starting basis is the example above with ridge and overriding slab + nx, nz = nx, ny + x = range(-2000, 2000, nx); + z = range(-660, 0, nz); + Grid2D = CartData(xyz_grid(x,0,z)) + Phases = zeros(Int64, nx, 1, nz); + Temp = fill(1280.0, nx, 1, nz); + air_thickness = 20.0 + # lith = LithosphericPhases(Layers=[15 20 55], Phases=[3 4 5], Tlab=1250) + lith = LithosphericPhases(Layers=[20 80], Phases=[1 2 0]) + # mantle = LithosphericPhases(Phases=[1]) + + # add_box!(Phases, Temp, Grid2D; xlim=(-1000, 1000), zlim=(-600.0, 0.0), phase = lith, T=HalfspaceCoolingTemp(Age=80)); + # Phases .= 0 + + # Add left oceanic plate + add_box!( + Phases, + Temp, + Grid2D; + xlim =(-2000, 0), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = lith, + T = SpreadingRateTemp( + Tsurface = 20, + Tmantle = 1280.0, + MORside = "left", + SpreadingVel= 0.5, + AgeRidge = 0.01; + maxAge = 80.0 + ) + ) + + # Add right oceanic plate + add_box!( + Phases, + Temp, + Grid2D; + xlim =(1500, 2000), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = lith, + T = SpreadingRateTemp( + Tsurface = 20, + Tmantle = 1280.0, + MORside = "right", + SpreadingVel= 0.5, + AgeRidge = 0.01; + maxAge = 80.0 + ) + ) + + # Add overriding plate margin + add_box!( + Phases, + Temp, + Grid2D; + xlim =(0, 400), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[25 90], Phases=[3 4 0] ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = 1280.0, + Age = 80.0 + ) + ) + + # Add overriding plate craton + add_box!( + Phases, + Temp, + Grid2D; + xlim =(400, 1500), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[35 100], Phases=[3 4 0] ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = 1280.0, + Age = 120.0 + ) + ) + # Add slab + add_box!( + Phases, + Temp, + Grid2D; + xlim =(0, 300), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=30, + phase = LithosphericPhases(Layers=[30 80], Phases=[1 2 0], Tlab=1250 ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = 1280.0, + Age = 120.0 + ) + ) + + Adiabat = 0.4 + @. Temp = Temp - Grid2D.z.val .* Adiabat + + # Lithosphere-asthenosphere boundary: + # ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); + # Phases[ind] .= 0; + + # surf = Grid2D.z.val .> 0.0 + # Temp[surf] .= 0.0 + # # Phases[surf] .= 7 + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + + li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 + origin = (x[1], z[1]) .* 1e3 + + ph = Phases[:,1,:] .+ 1 + + return li, origin, ph, Temp[:,1,:].+273 +end From 3f4422f75b59fe9880f29ebe7bc8a9a8c7f9e2ab Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Tue, 23 Apr 2024 17:44:20 +0200 Subject: [PATCH 31/60] update script --- subduction/LAMEM_rheology.jl | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/subduction/LAMEM_rheology.jl b/subduction/LAMEM_rheology.jl index d3d2c22f..3094fe06 100644 --- a/subduction/LAMEM_rheology.jl +++ b/subduction/LAMEM_rheology.jl @@ -42,7 +42,7 @@ function init_rheologies() ( disl_dry_olivine, diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), + # ConstantElasticity(; G=5e10, ν=0.5), DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity ) ), @@ -59,28 +59,30 @@ function init_rheologies() CompositeRheology = CompositeRheology( ( disl_oceanic_crust, - ConstantElasticity(; G=5e10, ν=0.5), + # ConstantElasticity(; G=5e10, ν=0.5), DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), - RadioactiveHeat = ConstantRadioactiveHeat(2), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + RadioactiveHeat = ConstantRadioactiveHeat(2.333e-10), + # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "oceanic lithosphere", SetMaterialParams(; Phase = 3, Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k =3 ), + Conductivity = ConstantConductivity(; k = 3), CompositeRheology = CompositeRheology( - ( - disl_oceanic_crust, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_oceanic_litho, ϕ = ϕ_oceanic_litho, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(2), + ( + disl_dry_olivine, + diff_dry_olivine, + # ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "continental crust", SetMaterialParams(; @@ -92,11 +94,11 @@ function init_rheologies() CompositeRheology = CompositeRheology( ( disl_cont_crust, - ConstantElasticity(; G=5e10, ν=0.5), + # ConstantElasticity(; G=5e10, ν=0.5), DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity ) ), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "continental lithosphere", SetMaterialParams(; @@ -108,12 +110,12 @@ function init_rheologies() ( disl_dry_olivine, diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), + # ConstantElasticity(; G=5e10, ν=0.5), DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), ) end From 7afa62548becd502dd0232f67428b898c2b18766 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 23 Apr 2024 23:53:53 +0200 Subject: [PATCH 32/60] up miniapps --- .../Layered_convection2D.jl | 4 +- src/stokes/Stokes2D.jl | 16 +- subduction/LAMEM2D.jl | 56 +-- subduction/LAMEM2D_nondim.jl | 368 ++++++++++++++++++ subduction/LAMEM_rheology.jl | 17 +- subduction/LAMEM_rheology_nondim.jl | 173 ++++++++ 6 files changed, 593 insertions(+), 41 deletions(-) create mode 100644 subduction/LAMEM2D_nondim.jl create mode 100644 subduction/LAMEM_rheology_nondim.jl diff --git a/miniapps/convection/Particles2D_nonDim/Layered_convection2D.jl b/miniapps/convection/Particles2D_nonDim/Layered_convection2D.jl index 61a0afdb..f21a9dae 100644 --- a/miniapps/convection/Particles2D_nonDim/Layered_convection2D.jl +++ b/miniapps/convection/Particles2D_nonDim/Layered_convection2D.jl @@ -204,7 +204,7 @@ function main2D(igg; ar=8, ny=16, nx=ny*8, figdir="figs2D", do_vtk =false) ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv) - scatter!(ax2, Array(log10.(A[:])), Y) + scatter!(ax2, Array(log10.(η[:])), Y) ylims!(ax1, minimum(xvi[2]), 0) ylims!(ax2, minimum(xvi[2]), 0) hideydecorations!(ax2) @@ -395,4 +395,4 @@ else end # run main script -main2D(igg; figdir = figdir, ar = ar, nx = nx, ny = ny, do_vtk = do_vtk); +# main2D(igg; figdir = figdir, ar = ar, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 3be0b181..4e045f0c 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -556,6 +556,7 @@ function JustRelax.solve!( wtime0 += @elapsed begin compute_maxloc!(ητ, η; window=(1, 1)) + # compute_maxloc!(ητ, η_vep; window=(1, 1)) update_halo!(ητ) @parallel (@idx ni) compute_∇V!(stokes.∇V, @velocity(stokes)..., _di...) @@ -628,16 +629,25 @@ function JustRelax.solve!( ) @hide_communication b_width begin # communication/computation overlap + # @parallel compute_V!( + # @velocity(stokes)..., + # Vx_on_Vy, + # θ, + # @stress(stokes)..., + # pt_stokes.ηdτ, + # ρg..., + # ητ, + # _di..., + # dt * free_surface, + # ) @parallel compute_V!( @velocity(stokes)..., - Vx_on_Vy, - θ, + stokes.P, @stress(stokes)..., pt_stokes.ηdτ, ρg..., ητ, _di..., - dt * free_surface, ) # apply boundary conditions free_surface_bcs!(stokes, flow_bcs, η, rheology, phase_ratios, dt, di) diff --git a/subduction/LAMEM2D.jl b/subduction/LAMEM2D.jl index afcde93f..89eec037 100644 --- a/subduction/LAMEM2D.jl +++ b/subduction/LAMEM2D.jl @@ -1,21 +1,21 @@ -using CUDA +# using CUDA using JustRelax, JustRelax.DataIO import JustRelax.@cell using ParallelStencil -# @init_parallel_stencil(Threads, Float64, 2) -@init_parallel_stencil(CUDA, Float64, 2) +@init_parallel_stencil(Threads, Float64, 2) +# @init_parallel_stencil(CUDA, Float64, 2) using JustPIC using JustPIC._2D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend -const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend # setup ParallelStencil.jl environment -# model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) -model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) environment!(model) # Load script dependencies @@ -93,8 +93,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # ---------------------------------------------------- # TEMPERATURE PROFILE -------------------------------- - Ttop = 20 - Tbot = 1565.0 + Ttop = 20 + 273 + Tbot = 1565.0 + 273 thermal = ThermalArrays(ni) @views thermal.T[2:end-1, :] .= PTArray(T_GMG) thermal_bc = TemperatureBoundaryConditions(; @@ -134,22 +134,22 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk flow_bcs!(stokes, flow_bcs) # apply boundary conditions update_halo!(@velocity(stokes)...) - # # Plot initial T and η profiles - # let - # Yv = [y for x in xvi[1], y in xvi[2]][:] - # Y = [y for x in xci[1], y in xci[2]][:] - # fig = Figure(size = (1200, 900)) - # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") - # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") - # scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) - # scatter!(ax2, Array(log10.(η[:])), Y./1e3) - # # scatter!(ax2, Array(ρg[2][:]), Y./1e3) - # ylims!(ax1, minimum(xvi[2])./1e3, 0) - # ylims!(ax2, minimum(xvi[2])./1e3, 0) - # hideydecorations!(ax2) - # # save(joinpath(figdir, "initial_profile.png"), fig) - # fig - # end + # Plot initial T and η profiles + let + Yv = [y for x in xvi[1], y in xvi[2]][:] + Y = [y for x in xci[1], y in xci[2]][:] + fig = Figure(size = (1200, 900)) + ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # scatter!(ax2, Array(ρg[2][:]), Y./1e3) + ylims!(ax1, minimum(xvi[2])./1e3, 0) + ylims!(ax2, minimum(xvi[2])./1e3, 0) + hideydecorations!(ax2) + # save(joinpath(figdir, "initial_profile.png"), fig) + fig + end # IO ------------------------------------------------- # if it does not exist, make folder where figures are stored @@ -213,8 +213,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk args, dt, igg; - iterMax = 150e3, - nout = 2e3, + iterMax = 100e3, + nout = 1e3, viscosity_cutoff = (1e18, 1e24), free_surface = false, # viscosity_relaxation = 1e-5 @@ -354,5 +354,5 @@ else igg end -main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); +# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/LAMEM2D_nondim.jl b/subduction/LAMEM2D_nondim.jl new file mode 100644 index 00000000..d43357c9 --- /dev/null +++ b/subduction/LAMEM2D_nondim.jl @@ -0,0 +1,368 @@ +# using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +@init_parallel_stencil(Threads, Float64, 2) +# @init_parallel_stencil(CUDA, Float64, 2) + +using JustPIC +using JustPIC._2D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("LAMEM_rheology_nondim.jl") +include("LAMEM_setup2D.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[2] +macro all_k(A) + esc(:($A[$idx_k])) +end + +function copyinn_x!(A, B) + @parallel function f_x(A, B) + @all(A) = @inn_x(B) + return nothing + end + + @parallel f_x(A, B) +end + + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) + + thickness = 660 * km + η0 = 1e20Pa*s + CharDim = GEO_units(; + length = thickness, viscosity = η0, temperature = 1e3C + ) + + # Physical domain ------------------------------------ + ni = nx, ny # number of cells + li_nd = nondimensionalize(li[1]m, CharDim), nondimensionalize(li[2]m, CharDim) + origin_nd = nondimensionalize(origin[1]m, CharDim), nondimensionalize(origin[2]m, CharDim) + di = @. li_nd / ni # grid steps + grid = Geometry(ni, li_nd; origin = origin_nd) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies(CharDim) + dt = nondimensionalize(10e3 * 3600 * 24 * 365 * s, CharDim) # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi, di, ni + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pPhases, pT = init_cell_arrays(particles, Val(2)) + particle_args = (pPhases, pT) + + # Assign particles phases anomaly + phases_device = PTArray(phases_GMG) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.1 / √2.1) + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + Ttop = nondimensionalize(20e0C, CharDim) + Tbot = nondimensionalize(1565e0C, CharDim) + thermal = ThermalArrays(ni) + @views thermal.T[2:end-1, :] .= PTArray(nondimensionalize(T_GMG .* K, CharDim)) + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = false, bot = false), + ) + thermal_bcs!(thermal, thermal_bc) + # @views thermal.T[:, end] .= Ttop + # @views thermal.T[:, 1] .= Tbot + @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(2)) + # for _ in 1:2 + # compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) + # end + + # Rheology + η = @ones(ni...) + η_vep = similar(η) + args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + viscosity_cutoff = nondimensionalize((1e16Pa*s, 1e24Pa*s), CharDim) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, viscosity_cutoff + ) + + # PT coefficients for thermal diffusion + pt_thermal = PTThermalCoeffs( + rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-2 / √3 + ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true), + free_surface = false, + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(@velocity(stokes)...) + + # Plot initial T and η profiles + let + Yv = [y for x in xvi[1], y in xvi[2]][:] + Y = [y for x in xci[1], y in xci[2]][:] + fig = Figure(size = (1200, 900)) + ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # scatter!(ax2, Array(ρg[2][:]), Y./1e3) + ylims!(ax1, minimum(xvi[2])./1e3, 0) + ylims!(ax2, minimum(xvi[2])./1e3, 0) + hideydecorations!(ax2) + # save(joinpath(figdir, "initial_profile.png"), fig) + fig + end + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + end + + T_buffer = @zeros(ni.+1) + Told_buffer = similar(T_buffer) + dt₀ = similar(stokes.P) + for (dst, src) in zip((T_buffer, Told_buffer), (thermal.T, thermal.Told)) + copyinn_x!(dst, src) + end + grid2particle!(pT, xvi, T_buffer, particles) + + # Time loop + t, it = 0.0, 0 + + while it < 1000 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # interpolate fields from particle to grid vertices + particle2grid!(T_buffer, pT, xvi, particles) + # @views T_buffer[:, end] .= Ttop + # @views T_buffer[:, 1] .= Tbot + @views thermal.T[2:end-1, :] .= T_buffer + thermal_bcs!(thermal, thermal_bc) + temperature2center!(thermal) + + # interpolate fields from particle to grid vertices + # particle2grid!(thermal.T, pT, xvi, particles) + # temperature2center!(thermal) + # Update buoyancy and viscosity - + args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, viscosity_cutoff + ) + compute_ρg!(ρg[2], phase_ratios, rheology, args) + + # Stokes solver ---------------- + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + Inf, + igg; + iterMax = 1, + nout = 1, + viscosity_cutoff = viscosity_cutoff, + free_surface = false, + viscosity_relaxation = 1e-3 + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") + @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.τ.II, @strain(stokes)...) + dt = compute_dt(stokes, di) + # ------------------------------ + + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology, + args, + dt, + di; + igg = igg, + phase = phase_ratios, + iterMax = 10e3, + nout = 1e2, + verbose = true, + ) + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer,), xvi) + # update phase ratios + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 5) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) + data_v = (; + T = Array(thermal.T), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + ) + data_c = (; + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(η), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + # Make particles plottable + p = particles.coords + ppx, ppy = p + pxv = ppx.data[:]./1e3 + pyv = ppy.data[:]./1e3 + clr = pPhases.data[:] + # clr = pT.data[:] + idxv = particles.index.data[:]; + + # Make Makie figure + ar = 3 + fig = Figure(size = (1200, 900), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "T [K] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "Vy [m/s]") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) + # Plot particles phase + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η_vep)) , colormap=:batlow) + hidexdecorations!(ax1) + hidexdecorations!(ax2) + hidexdecorations!(ax3) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end + +## END OF MAIN SCRIPT ---------------------------------------------------------------- +do_vtk = true # set to true to generate VTK files for ParaView +figdir = "Subduction_LAMEM_2D" +# nx, ny = 512, 256 +# nx, ny = 512, 128 +n = 64 +nx, ny = n*6, n +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) +else + igg +end + +# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/LAMEM_rheology.jl b/subduction/LAMEM_rheology.jl index d3d2c22f..de9df53a 100644 --- a/subduction/LAMEM_rheology.jl +++ b/subduction/LAMEM_rheology.jl @@ -71,15 +71,16 @@ function init_rheologies() Phase = 3, Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k =3 ), + Conductivity = ConstantConductivity(; k = 3), CompositeRheology = CompositeRheology( - ( - disl_oceanic_crust, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_oceanic_litho, ϕ = ϕ_oceanic_litho, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(2), + ( + disl_dry_olivine, + diff_dry_olivine, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "continental crust", diff --git a/subduction/LAMEM_rheology_nondim.jl b/subduction/LAMEM_rheology_nondim.jl new file mode 100644 index 00000000..d01b83b6 --- /dev/null +++ b/subduction/LAMEM_rheology_nondim.jl @@ -0,0 +1,173 @@ +using GeoParams.Diffusion +using GeoParams.Dislocation + +function init_rheologies(CharDim) + disl_dry_olivine = SetDislocationCreep(Dislocation.dry_olivine_Hirth_2003; V = 14.5e-6m^3 / mol) + disl_oceanic_crust = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) + # disl_oceanic_litho = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) + disl_cont_crust = SetDislocationCreep(Dislocation.wet_quartzite_Kirby_1983) + + Transform_DislocationCreep(Dislocation.wet_quartzite_Kirby_1983) + + diff_dry_olivine = SetDiffusionCreep(Diffusion.dry_olivine_Hirth_2003; V = 14.5e-6m^3 / mol) + + ϕ_dry_olivine = sind(20) + C_dry_olivine = 30e6Pa + + ϕ_oceanic_crust = sind(0) + C_oceanic_crust = 5e6Pa + + ϕ_oceanic_litho = sind(0) + C_oceanic_litho = 5e6Pa + + ϕ_cont_crust = sind(20) + C_cont_crust = 30e6Pa + + soft_C = LinearSoftening((C_oceanic_litho.val*0.95, C_oceanic_litho.val), (0.1, 0.5)) + + # common physical properties + α = 3e-5 / K + Cp = 1000 * J / kg * K + η_reg = 1e18Pa * s + + + # Define rheolgy struct + rheology = ( + # Name = "dry olivine - Hirth_Kohlstedt_2003", + SetMaterialParams(; + Phase = 1, + Density = PT_Density(; ρ0=3.3e3kg / m^3, α = α, β = 0e0 / Pa, T0 = 273K), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3Watt/m/K), + CompositeRheology = CompositeRheology( + ( + LinearViscous(;η=1e19Pa*s), + # disl_dry_olivine, + # diff_dry_olivine, + # ConstantElasticity(; G=5e10Pa, ν=0.5), + # DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12Watt/kg), + # Elasticity = ConstantElasticity(; G=5e10Pa, ν=0.5), + Gravity = ConstantGravity(; g=9.81m/s^2), + CharDim = CharDim + ), + # Name = "oceanic crust", + SetMaterialParams(; + Phase = 2, + Density = PT_Density(; ρ0=3.3e3kg / m^3, α = α, β = 0e0 / Pa, T0 = 273K), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3Watt/m/K), + CompositeRheology = CompositeRheology( + ( + LinearViscous(;η=1e20Pa*s), + # disl_oceanic_crust, + # ConstantElasticity(; G=5e10Pa, ν=0.5), + # DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2.333e-10Watt/kg), + # Elasticity = ConstantElasticity(; G=5e10Pa, ν=0.5), + CharDim = CharDim + ), + # Name = "oceanic lithosphere", + SetMaterialParams(; + Phase = 3, + Density = PT_Density(; ρ0=3.3e3kg / m^3, α = α, β = 0e0 / Pa, T0 = 273K), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3Watt/m/K), + CompositeRheology = CompositeRheology( + ( + LinearViscous(;η=1e19Pa*s), + # disl_dry_olivine, + # diff_dry_olivine, + # ConstantElasticity(; G=5e10Pa, ν=0.5), + # DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12Watt/kg), + # Elasticity = ConstantElasticity(; G=5e10Pa, ν=0.5), + CharDim = CharDim + ), + # Name = "continental crust", + SetMaterialParams(; + Phase = 4, + Density = PT_Density(; ρ0=2.7e3kg / m^3, α = α, β = 0e0 / Pa, T0 = 273K), + RadioactiveHeat = ConstantRadioactiveHeat(5.3571e-10Watt/kg), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3Watt/m/K), + CompositeRheology = CompositeRheology( + ( + LinearViscous(;η=1e21Pa*s), + # disl_cont_crust, + # ConstantElasticity(; G=5e10Pa, ν=0.5), + # DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + # Elasticity = ConstantElasticity(; G=5e10Pa, ν=0.5), + CharDim = CharDim + ), + # Name = "continental lithosphere", + SetMaterialParams(; + Phase = 5, + Density = PT_Density(; ρ0=3.3e3kg / m^3, α = α, β = 0e0 / Pa, T0 = 273K), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3Watt/m/K), + CompositeRheology = CompositeRheology( + ( + LinearViscous(;η=1e19Pa*s), + # disl_dry_olivine, + # diff_dry_olivine, + # ConstantElasticity(; G=5e10Pa, ν=0.5), + # DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12Watt/kg), + # Elasticity = ConstantElasticity(; G=5e10Pa, ν=0.5), + CharDim = CharDim + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + @cell(index[ip, I...]) == 0 && continue + + pᵢ = ntuple(Val(N)) do i + @cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1 + ii = I[1] + offi + jj = I[2] + offj + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + ) + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj] + end + end + @cell phases[ip, I...] = Float64(particle_phase) + end + + return nothing +end From 10907b266cfaea559962d2a4c3f461497ec9e74f Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 24 Apr 2024 17:01:01 +0200 Subject: [PATCH 33/60] remove P gradient --- src/stokes/Stokes2D.jl | 35 +++++++++--------- subduction/LAMEM2D.jl | 69 +++++++++++++++++++++--------------- subduction/LAMEM_rheology.jl | 41 ++++++++++----------- 3 files changed, 76 insertions(+), 69 deletions(-) diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 4e045f0c..d7fc5891 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -550,13 +550,16 @@ function JustRelax.solve!( Aij .= 0.0 end Vx_on_Vy = @zeros(size(stokes.V.Vy)) - + + ρg_bg = 2700 * 9.81 + Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg) .* di[2], dims=2), dims=2), dims=2) + args.P .= stokes.P .+ Plitho .- minimum(stokes.P) + while iter ≤ iterMax iterMin < iter && err < ϵ && break wtime0 += @elapsed begin compute_maxloc!(ητ, η; window=(1, 1)) - # compute_maxloc!(ητ, η_vep; window=(1, 1)) update_halo!(ητ) @parallel (@idx ni) compute_∇V!(stokes.∇V, @velocity(stokes)..., _di...) @@ -583,11 +586,12 @@ function JustRelax.solve!( if rem(iter, 5) == 0 @parallel (@idx ni) compute_ρg!(ρg[2], phase_ratios.center, rheology, args) end + # args.P .= reverse(cumsum(reverse(ρg[2] .+ ρg_bg, dims=2), dims=2), dims=2) @parallel (@idx ni .+ 1) compute_strain_rate!( @strain(stokes)..., stokes.∇V, @velocity(stokes)..., _di... ) - + if rem(iter, nout) == 0 @copy η0 η end @@ -609,7 +613,8 @@ function JustRelax.solve!( @strain(stokes), @tensor_center(stokes.ε_pl), stokes.EII_pl, - stokes.P, + args.P, + # stokes.P, θ, η, η_vep, @@ -619,6 +624,7 @@ function JustRelax.solve!( dt, θ_dτ, ) + # θ .-= args.P free_surface_bcs!(stokes, flow_bcs) @parallel center2vertex!(stokes.τ.xy, stokes.τ.xy_c) @@ -629,25 +635,17 @@ function JustRelax.solve!( ) @hide_communication b_width begin # communication/computation overlap - # @parallel compute_V!( - # @velocity(stokes)..., - # Vx_on_Vy, - # θ, - # @stress(stokes)..., - # pt_stokes.ηdτ, - # ρg..., - # ητ, - # _di..., - # dt * free_surface, - # ) @parallel compute_V!( @velocity(stokes)..., + Vx_on_Vy, stokes.P, @stress(stokes)..., pt_stokes.ηdτ, - ρg..., + ρg[1], + ρg[2], ητ, _di..., + dt * free_surface, ) # apply boundary conditions free_surface_bcs!(stokes, flow_bcs, η, rheology, phase_ratios, dt, di) @@ -668,7 +666,8 @@ function JustRelax.solve!( Vx_on_Vy, stokes.P, @stress(stokes)..., - ρg..., + ρg[1], + ρg[2], _di..., dt * free_surface, ) @@ -703,7 +702,7 @@ function JustRelax.solve!( end end - stokes.P .= θ + # stokes.P .= θ # @views stokes.P .-= stokes.P[:, end] # accumulate plastic strain tensor diff --git a/subduction/LAMEM2D.jl b/subduction/LAMEM2D.jl index 89eec037..55f81d90 100644 --- a/subduction/LAMEM2D.jl +++ b/subduction/LAMEM2D.jl @@ -1,21 +1,21 @@ -# using CUDA +using CUDA using JustRelax, JustRelax.DataIO import JustRelax.@cell using ParallelStencil -@init_parallel_stencil(Threads, Float64, 2) -# @init_parallel_stencil(CUDA, Float64, 2) +# @init_parallel_stencil(Threads, Float64, 2) +@init_parallel_stencil(CUDA, Float64, 2) using JustPIC using JustPIC._2D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend -# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend # setup ParallelStencil.jl environment -model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) -# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) environment!(model) # Load script dependencies @@ -42,12 +42,12 @@ function copyinn_x!(A, B) @parallel f_x(A, B) end - # Initial pressure profile - not accurate @parallel function init_P!(P, ρg, z) @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) return nothing end + ## END OF HELPER FUNCTION ------------------------------------------------------------ ## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- @@ -89,7 +89,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # STOKES --------------------------------------------- # Allocate arrays needed for every Stokes problem stokes = StokesArrays(ni, ViscoElastic) - pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 0.99 / √2.1) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, Re=3π, r=1e0, CFL = 1 / √2.1) # Re=3π, r=0.7 # ---------------------------------------------------- # TEMPERATURE PROFILE -------------------------------- @@ -108,22 +108,29 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Buoyancy forces ρg = ntuple(_ -> @zeros(ni...), Val(2)) - for _ in 1:2 - compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) - JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) - end + # for _ in 1:2 + # compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) + # end + compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # Plitho = reverse(cumsum(reverse(ρg[2] .+ (2700*9.81), dims=2), dims=2), dims=2) + ρg_bg = 2700 * 9.81 + # args.P .= reverse(cumsum(reverse(ρg[2] .+ ρg_bg, dims=2), dims=2), dims=2) + Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) + # args.P = stokes.P .+ Plitho .- minimum(stokes.P) + # Rheology η = @ones(ni...) η_vep = similar(η) - args = (; T = thermal.Tc, P = stokes.P, dt = Inf) + args0 = (; T = thermal.Tc, P = Plitho, dt = Inf) compute_viscosity!( - η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + η, 1.0, phase_ratios, stokes, args0, rheology, (1e18, 1e24) ) # PT coefficients for thermal diffusion pt_thermal = PTThermalCoeffs( - rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-2 / √3 + rheology, phase_ratios, args0, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 ) # Boundary conditions @@ -142,8 +149,9 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) - scatter!(ax2, Array(log10.(η[:])), Y./1e3) - # scatter!(ax2, Array(ρg[2][:]), Y./1e3) + # scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # scatter!(ax2, Array(stokes.P[:]), Y./1e3) + scatter!(ax2, Array(Plitho[:]), Y./1e3) ylims!(ax1, minimum(xvi[2])./1e3, 0) ylims!(ax2, minimum(xvi[2])./1e3, 0) hideydecorations!(ax2) @@ -188,11 +196,13 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk thermal_bcs!(thermal, thermal_bc) temperature2center!(thermal) - # interpolate fields from particle to grid vertices - # particle2grid!(thermal.T, pT, xvi, particles) - # temperature2center!(thermal) # Update buoyancy and viscosity - - args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + Plitho .= reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) + # Plitho .= -(ρg[2] .+ ρg_bg) .* xci[2]' + Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) + # args.P .= 0 + + args = (; T = thermal.Tc, P = Plitho, dt=Inf) compute_viscosity!( η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) ) @@ -214,10 +224,10 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk dt, igg; iterMax = 100e3, - nout = 1e3, + nout = 2e3, viscosity_cutoff = (1e18, 1e24), free_surface = false, - # viscosity_relaxation = 1e-5 + viscosity_relaxation = 1e-2 ); end println("Stokes solver time ") @@ -271,7 +281,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk if do_vtk JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) data_v = (; - T = Array(thermal.T), + T = Array(T_buffer), τxy = Array(stokes.τ.xy), εxy = Array(stokes.ε.xy), Vx = Array(Vx_v), @@ -283,7 +293,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk τyy = Array(stokes.τ.yy), εxx = Array(stokes.ε.xx), εyy = Array(stokes.ε.yy), - η = Array(η), + η = Array(η_vep), ) velocity_v = ( Array(Vx_v), @@ -346,7 +356,9 @@ do_vtk = true # set to true to generate VTK files for ParaView figdir = "Subduction_LAMEM_2D" # nx, ny = 512, 256 # nx, ny = 512, 128 -nx, ny = 384, 64 +n = 64 +nx, ny = n*6, n +nx, ny = 512, 128 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) @@ -354,5 +366,4 @@ else igg end -# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); - +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/LAMEM_rheology.jl b/subduction/LAMEM_rheology.jl index 31f68590..cb6e4503 100644 --- a/subduction/LAMEM_rheology.jl +++ b/subduction/LAMEM_rheology.jl @@ -15,35 +15,35 @@ function init_rheologies() ϕ_oceanic_crust = sind(0) C_oceanic_crust = 5e6 - ϕ_oceanic_litho = sind(0) + ϕ_oceanic_litho = sind(10) C_oceanic_litho = 5e6 ϕ_cont_crust = sind(20) C_cont_crust = 30e6 - soft_C = LinearSoftening((C_oceanic_litho*0.95, C_oceanic_litho), (0.1, 0.5)) + soft_C = LinearSoftening((C_oceanic_litho*0.05, C_oceanic_litho), (0.1, 0.5)) # common physical properties α = 3e-5 # 1 / K Cp = 1000 # J / kg K # C = 3e6 # Pa - η_reg = 1e18 - + η_reg = 1e20 + ρbg = 2700 # kg / m^3 # Define rheolgy struct rheology = ( # Name = "dry olivine - Hirth_Kohlstedt_2003", SetMaterialParams(; Phase = 1, - Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), Conductivity = ConstantConductivity(; k = 3), CompositeRheology = CompositeRheology( ( disl_dry_olivine, diff_dry_olivine, - # ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), @@ -53,70 +53,67 @@ function init_rheologies() # Name = "oceanic crust", SetMaterialParams(; Phase = 2, - Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), Conductivity = ConstantConductivity(; k =3 ), CompositeRheology = CompositeRheology( ( disl_oceanic_crust, - # ConstantElasticity(; G=5e10, ν=0.5), + ConstantElasticity(; G=5e10, ν=0.5), DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(2.333e-10), - # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "oceanic lithosphere", SetMaterialParams(; Phase = 3, - Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), Conductivity = ConstantConductivity(; k = 3), - Conductivity = ConstantConductivity(; k = 3), CompositeRheology = CompositeRheology( ( disl_dry_olivine, diff_dry_olivine, ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), Elasticity = ConstantElasticity(; G=5e10, ν=0.5), - # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "continental crust", SetMaterialParams(; Phase = 4, - Density = PT_Density(; ρ0=2.7e3, α = α, β = 0e0, T0 = 273), + Density = PT_Density(; ρ0=2.7e3-ρbg, α = α, β = 0e0, T0 = 273), RadioactiveHeat = ConstantRadioactiveHeat(5.3571e-10), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), Conductivity = ConstantConductivity(; k =3 ), CompositeRheology = CompositeRheology( ( disl_cont_crust, - # ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), - # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), # Name = "continental lithosphere", SetMaterialParams(; Phase = 5, - Density = PT_Density(; ρ0=3.3e3, α = α, β = 0e0, T0 = 273), + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), HeatCapacity = ConstantHeatCapacity(; Cp=Cp), Conductivity = ConstantConductivity(; k = 3), CompositeRheology = CompositeRheology( ( disl_dry_olivine, diff_dry_olivine, - # ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - # Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), ) end From 616aa2b81f973d9edb699ac47d0261f3429e37b3 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Thu, 25 Apr 2024 00:01:47 +0200 Subject: [PATCH 34/60] up LAMEM example --- subduction/{ => 2D}/LAMEM2D.jl | 16 +- subduction/{ => 2D}/LAMEM2D_nondim.jl | 0 subduction/2D/LAMEM2D_sticky.jl | 370 +++++++++++++++++++ subduction/{ => 2D}/LAMEM_rheology.jl | 135 +++++++ subduction/{ => 2D}/LAMEM_rheology_nondim.jl | 0 subduction/{ => 2D}/LAMEM_setup2D.jl | 0 subduction/2D/LAMEM_setup2D_sticky.jl | 118 ++++++ 7 files changed, 631 insertions(+), 8 deletions(-) rename subduction/{ => 2D}/LAMEM2D.jl (96%) rename subduction/{ => 2D}/LAMEM2D_nondim.jl (100%) create mode 100644 subduction/2D/LAMEM2D_sticky.jl rename subduction/{ => 2D}/LAMEM_rheology.jl (51%) rename subduction/{ => 2D}/LAMEM_rheology_nondim.jl (100%) rename subduction/{ => 2D}/LAMEM_setup2D.jl (100%) create mode 100644 subduction/2D/LAMEM_setup2D_sticky.jl diff --git a/subduction/LAMEM2D.jl b/subduction/2D/LAMEM2D.jl similarity index 96% rename from subduction/LAMEM2D.jl rename to subduction/2D/LAMEM2D.jl index 55f81d90..13432ed4 100644 --- a/subduction/LAMEM2D.jl +++ b/subduction/2D/LAMEM2D.jl @@ -1,21 +1,21 @@ -using CUDA +# using CUDA using JustRelax, JustRelax.DataIO import JustRelax.@cell using ParallelStencil -# @init_parallel_stencil(Threads, Float64, 2) -@init_parallel_stencil(CUDA, Float64, 2) +@init_parallel_stencil(Threads, Float64, 2) +# @init_parallel_stencil(CUDA, Float64, 2) using JustPIC using JustPIC._2D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend -const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend # setup ParallelStencil.jl environment -# model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) -model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) environment!(model) # Load script dependencies @@ -366,4 +366,4 @@ else igg end -main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); +# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/LAMEM2D_nondim.jl b/subduction/2D/LAMEM2D_nondim.jl similarity index 100% rename from subduction/LAMEM2D_nondim.jl rename to subduction/2D/LAMEM2D_nondim.jl diff --git a/subduction/2D/LAMEM2D_sticky.jl b/subduction/2D/LAMEM2D_sticky.jl new file mode 100644 index 00000000..3c0eaf3f --- /dev/null +++ b/subduction/2D/LAMEM2D_sticky.jl @@ -0,0 +1,370 @@ +# using CUDA +using JustRelax, JustRelax.DataIO +import JustRelax.@cell +using ParallelStencil +@init_parallel_stencil(Threads, Float64, 2) +# @init_parallel_stencil(CUDA, Float64, 2) + +using JustPIC +using JustPIC._2D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +# setup ParallelStencil.jl environment +model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +environment!(model) + +# Load script dependencies +using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("LAMEM_rheology.jl") +include("LAMEM_setup2D_sticky.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[2] +macro all_k(A) + esc(:($A[$idx_k])) +end + +function copyinn_x!(A, B) + @parallel function f_x(A, B) + @all(A) = @inn_x(B) + return nothing + end + + @parallel f_x(A, B) +end + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end + +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) + + # Physical domain ------------------------------------ + ni = nx, ny # number of cells + di = @. li / ni # grid steps + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + rheology = init_rheologies() + rheology_augmented = init_augmented_rheologies() + dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( + backend, nxcell, max_xcell, min_xcell, xvi, di, ni + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pPhases, pT = init_cell_arrays(particles, Val(2)) + particle_args = (pPhases, pT) + + # Assign particles phases anomaly + phases_device = PTArray(phases_GMG) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios = PhaseRatio(ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(ni, ViscoElastic) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, Re=3π, r=1e0, CFL = 1 / √2.1) # Re=3π, r=0.7 + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + Ttop = 20 + 273 + Tbot = 1565.0 + 273 + thermal = ThermalArrays(ni) + @views thermal.T[2:end-1, :] .= PTArray(T_GMG) + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = false, bot = false), + ) + thermal_bcs!(thermal, thermal_bc) + @views thermal.T[:, end] .= Ttop + @views thermal.T[:, 1] .= Tbot + @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(2)) + # for _ in 1:2 + # compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) + # end + compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # Plitho = reverse(cumsum(reverse(ρg[2] .+ (2700*9.81), dims=2), dims=2), dims=2) + + ρg_bg = 2700 * 9.81 + # args.P .= reverse(cumsum(reverse(ρg[2] .+ ρg_bg, dims=2), dims=2), dims=2) + Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) + # args.P = stokes.P .+ Plitho .- minimum(stokes.P) + + # Rheology + η = @ones(ni...) + η_vep = similar(η) + args0 = (; T = thermal.Tc, P = Plitho, dt = Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args0, rheology, (1e18, 1e24) + ) + + # PT coefficients for thermal diffusion + pt_thermal = PTThermalCoeffs( + rheology_augmented, phase_ratios, args0, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 + ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true), + free_surface = false, + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(@velocity(stokes)...) + + # Plot initial T and η profiles + let + Yv = [y for x in xvi[1], y in xvi[2]][:] + Y = [y for x in xci[1], y in xci[2]][:] + fig = Figure(size = (1200, 900)) + ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + # scatter!(ax2, Array(log10.(η[:])), Y./1e3) + # scatter!(ax2, Array(stokes.P[:]), Y./1e3) + scatter!(ax2, Array(Plitho[:]), Y./1e3) + ylims!(ax1, minimum(xvi[2])./1e3, 0) + ylims!(ax2, minimum(xvi[2])./1e3, 0) + hideydecorations!(ax2) + # save(joinpath(figdir, "initial_profile.png"), fig) + fig + end + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + end + + T_buffer = @zeros(ni.+1) + Told_buffer = similar(T_buffer) + dt₀ = similar(stokes.P) + for (dst, src) in zip((T_buffer, Told_buffer), (thermal.T, thermal.Told)) + copyinn_x!(dst, src) + end + grid2particle!(pT, xvi, T_buffer, particles) + + # Time loop + t, it = 0.0, 0 + + while it < 1000 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # interpolate fields from particle to grid vertices + particle2grid!(T_buffer, pT, xvi, particles) + @views T_buffer[:, end] .= Ttop + @views T_buffer[:, 1] .= Tbot + @views thermal.T[2:end-1, :] .= T_buffer + thermal_bcs!(thermal, thermal_bc) + temperature2center!(thermal) + + # Update buoyancy and viscosity - + Plitho .= reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) + # Plitho .= -(ρg[2] .+ ρg_bg) .* xci[2]' + Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) + # args.P .= 0 + + args = (; T = thermal.Tc, P = Plitho, dt=Inf) + compute_viscosity!( + η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) + ) + compute_ρg!(ρg[2], phase_ratios, rheology, args) + + # Stokes solver ---------------- + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + η, + η_vep, + phase_ratios, + rheology, + args, + dt, + igg; + iterMax = 100e3, + nout = 2e3, + viscosity_cutoff = (1e18, 1e24), + free_surface = false, + viscosity_relaxation = 1e-2 + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") + @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) + dt = compute_dt(stokes, di) + # ------------------------------ + + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology_augmented, + args, + dt, + di; + igg = igg, + phase = phase_ratios, + iterMax = 10e3, + nout = 1e2, + verbose = true, + ) + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer,), xvi) + # update phase ratios + @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 5) == 0 + checkpointing(figdir, stokes, thermal.T, η, t) + + if do_vtk + JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) + data_v = (; + T = Array(T_buffer), + τxy = Array(stokes.τ.xy), + εxy = Array(stokes.ε.xy), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + ) + data_c = (; + P = Array(stokes.P), + τxx = Array(stokes.τ.xx), + τyy = Array(stokes.τ.yy), + εxx = Array(stokes.ε.xx), + εyy = Array(stokes.ε.yy), + η = Array(η_vep), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + # Make particles plottable + p = particles.coords + ppx, ppy = p + pxv = ppx.data[:]./1e3 + pyv = ppy.data[:]./1e3 + clr = pPhases.data[:] + # clr = pT.data[:] + idxv = particles.index.data[:]; + + # Make Makie figure + ar = 3 + fig = Figure(size = (1200, 900), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "T [K] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "Vy [m/s]") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) + # Plot particles phase + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η_vep)) , colormap=:batlow) + hidexdecorations!(ax1) + hidexdecorations!(ax2) + hidexdecorations!(ax3) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + save(joinpath(figdir, "$(it).png"), fig) + fig + end + # ------------------------------ + + end + + return nothing +end + +## END OF MAIN SCRIPT ---------------------------------------------------------------- +do_vtk = true # set to true to generate VTK files for ParaView +figdir = "Subduction_LAMEM_2D" +# nx, ny = 512, 256 +# nx, ny = 512, 128 +n = 64 +nx, ny = n*6, n +nx, ny = 512, 128 +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) +else + igg +end + +# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/LAMEM_rheology.jl b/subduction/2D/LAMEM_rheology.jl similarity index 51% rename from subduction/LAMEM_rheology.jl rename to subduction/2D/LAMEM_rheology.jl index cb6e4503..a194850d 100644 --- a/subduction/LAMEM_rheology.jl +++ b/subduction/2D/LAMEM_rheology.jl @@ -115,6 +115,141 @@ function init_rheologies() RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), Elasticity = ConstantElasticity(; G=5e10, ν=0.5), ), + # Name = "StickyAir", + SetMaterialParams(; + Phase = 6, + Density = ConstantDensity(; ρ=1e3-ρbg), # water density + HeatCapacity = ConstantHeatCapacity(; Cp=3e3), + RadioactiveHeat = ConstantRadioactiveHeat(0.0), + Conductivity = ConstantConductivity(; k=1.0), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e21),)), + ), + ) +end + +function init_augmented_rheologies() + disl_dry_olivine = SetDislocationCreep(Dislocation.dry_olivine_Hirth_2003; V = 14.5e-6) + disl_oceanic_crust = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) + # disl_oceanic_litho = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) + disl_cont_crust = SetDislocationCreep(Dislocation.wet_quartzite_Kirby_1983) + + diff_dry_olivine = SetDiffusionCreep(Diffusion.dry_olivine_Hirth_2003; V = 14.5e-6) + + ϕ_dry_olivine = sind(20) + C_dry_olivine = 30e6 + + ϕ_oceanic_crust = sind(0) + C_oceanic_crust = 5e6 + + ϕ_oceanic_litho = sind(10) + C_oceanic_litho = 5e6 + + ϕ_cont_crust = sind(20) + C_cont_crust = 30e6 + + soft_C = LinearSoftening((C_oceanic_litho*0.05, C_oceanic_litho), (0.1, 0.5)) + + # common physical properties + α = 3e-5 # 1 / K + Cp = 1000 # J / kg K + # C = 3e6 # Pa + η_reg = 1e20 + ρbg = 0 # kg / m^3 + + # Define rheolgy struct + rheology = ( + # Name = "dry olivine - Hirth_Kohlstedt_2003", + SetMaterialParams(; + Phase = 1, + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "oceanic crust", + SetMaterialParams(; + Phase = 2, + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + CompositeRheology = CompositeRheology( + ( + disl_oceanic_crust, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(2.333e-10), + ), + # Name = "oceanic lithosphere", + SetMaterialParams(; + Phase = 3, + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + # Name = "continental crust", + SetMaterialParams(; + Phase = 4, + Density = PT_Density(; ρ0=2.7e3-ρbg, α = α, β = 0e0, T0 = 273), + RadioactiveHeat = ConstantRadioactiveHeat(5.3571e-10), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k =3 ), + CompositeRheology = CompositeRheology( + ( + disl_cont_crust, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + # Name = "continental lithosphere", + SetMaterialParams(; + Phase = 5, + Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), + HeatCapacity = ConstantHeatCapacity(; Cp=Cp), + Conductivity = ConstantConductivity(; k = 3), + CompositeRheology = CompositeRheology( + ( + disl_dry_olivine, + diff_dry_olivine, + ConstantElasticity(; G=5e10, ν=0.5), + DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity + ) + ), + RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), + Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + ), + # Name = "StickyAir", + SetMaterialParams(; + Phase = 6, + Density = ConstantDensity(; ρ=1e3-ρbg), # water density + HeatCapacity = ConstantHeatCapacity(; Cp=3e3), + RadioactiveHeat = ConstantRadioactiveHeat(0.0), + Conductivity = ConstantConductivity(; k=1.0), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e21),)), + ), ) end diff --git a/subduction/LAMEM_rheology_nondim.jl b/subduction/2D/LAMEM_rheology_nondim.jl similarity index 100% rename from subduction/LAMEM_rheology_nondim.jl rename to subduction/2D/LAMEM_rheology_nondim.jl diff --git a/subduction/LAMEM_setup2D.jl b/subduction/2D/LAMEM_setup2D.jl similarity index 100% rename from subduction/LAMEM_setup2D.jl rename to subduction/2D/LAMEM_setup2D.jl diff --git a/subduction/2D/LAMEM_setup2D_sticky.jl b/subduction/2D/LAMEM_setup2D_sticky.jl new file mode 100644 index 00000000..b44b09bd --- /dev/null +++ b/subduction/2D/LAMEM_setup2D_sticky.jl @@ -0,0 +1,118 @@ +using GeophysicalModelGenerator + +function GMG_subduction_2D(nx, ny) + # Our starting basis is the example above with ridge and overriding slab + nx, nz = nx, ny + x = range(-2000, 2000, nx); + z = range(-660, 20, nz); + Grid2D = CartData(xyz_grid(x,0,z)) + Phases = zeros(Int64, nx, 1, nz); + Temp = fill(1280.0, nx, 1, nz); + air_thickness = 20.0 + lith = LithosphericPhases(Layers=[air_thickness+20 80], Phases=[1 2 0]) + + # Add left oceanic plate + add_box!( + Phases, + Temp, + Grid2D; + xlim =(-2000, 0), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = lith, + T = SpreadingRateTemp( + Tsurface = 20, + Tmantle = 1280.0, + MORside = "left", + SpreadingVel= 0.5, + AgeRidge = 0.01; + maxAge = 80.0 + ) + ) + + # Add right oceanic plate + add_box!( + Phases, + Temp, + Grid2D; + xlim =(1500, 2000), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = lith, + T = SpreadingRateTemp( + Tsurface = 20, + Tmantle = 1280.0, + MORside = "right", + SpreadingVel= 0.5, + AgeRidge = 0.01; + maxAge = 80.0 + ) + ) + + # Add overriding plate margin + add_box!( + Phases, + Temp, + Grid2D; + xlim =(0, 400), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[air_thickness+25 90], Phases=[3 4 0] ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = 1280.0, + Age = 80.0 + ) + ) + + # Add overriding plate craton + add_box!( + Phases, + Temp, + Grid2D; + xlim =(400, 1500), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[air_thickness+35 100], Phases=[3 4 0] ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = 1280.0, + Age = 120.0 + ) + ) + # Add slab + add_box!( + Phases, + Temp, + Grid2D; + xlim =(0, 300), + zlim =(-660.0, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=30, + phase = LithosphericPhases(Layers=[air_thickness+30 80], Phases=[1 2 0], Tlab=1250 ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = 1280.0, + Age = 120.0 + ) + ) + + Adiabat = 0.4 + @. Temp = Temp - Grid2D.z.val .* Adiabat + + # Lithosphere-asthenosphere boundary: + # ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); + # Phases[ind] .= 0; + + surf = Grid2D.z.val .> 0.0 + Temp[surf] .= 20.0 + Phases[surf] .= 5 + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + + li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 + origin = (x[1], z[1]) .* 1e3 + + ph = Phases[:,1,:] .+ 1 + + return li, origin, ph, Temp[:,1,:].+273 +end From de60d2d83d363509003d0fb3893856c8068ce0b9 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Thu, 25 Apr 2024 00:17:40 +0200 Subject: [PATCH 35/60] up --- subduction/2D/LAMEM2D_sticky.jl | 2 +- subduction/2D/LAMEM_rheology.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/subduction/2D/LAMEM2D_sticky.jl b/subduction/2D/LAMEM2D_sticky.jl index 3c0eaf3f..5d5b24f6 100644 --- a/subduction/2D/LAMEM2D_sticky.jl +++ b/subduction/2D/LAMEM2D_sticky.jl @@ -63,7 +63,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Physical properties using GeoParams ---------------- rheology = init_rheologies() rheology_augmented = init_augmented_rheologies() - dt = 10e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + dt = 1e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter # ---------------------------------------------------- # Initialize particles ------------------------------- diff --git a/subduction/2D/LAMEM_rheology.jl b/subduction/2D/LAMEM_rheology.jl index a194850d..67785e65 100644 --- a/subduction/2D/LAMEM_rheology.jl +++ b/subduction/2D/LAMEM_rheology.jl @@ -118,11 +118,11 @@ function init_rheologies() # Name = "StickyAir", SetMaterialParams(; Phase = 6, - Density = ConstantDensity(; ρ=1e3-ρbg), # water density + Density = ConstantDensity(; ρ=1e0-ρbg), # water density HeatCapacity = ConstantHeatCapacity(; Cp=3e3), RadioactiveHeat = ConstantRadioactiveHeat(0.0), Conductivity = ConstantConductivity(; k=1.0), - CompositeRheology = CompositeRheology((LinearViscous(; η=1e21),)), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e18),)), ), ) end @@ -244,11 +244,11 @@ function init_augmented_rheologies() # Name = "StickyAir", SetMaterialParams(; Phase = 6, - Density = ConstantDensity(; ρ=1e3-ρbg), # water density + Density = ConstantDensity(; ρ=1e0-ρbg), # water density HeatCapacity = ConstantHeatCapacity(; Cp=3e3), RadioactiveHeat = ConstantRadioactiveHeat(0.0), Conductivity = ConstantConductivity(; k=1.0), - CompositeRheology = CompositeRheology((LinearViscous(; η=1e21),)), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e18),)), ), ) end From f411654c5f3247e549929868d87fc16d790a4aec Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 25 Apr 2024 16:55:21 +0200 Subject: [PATCH 36/60] up --- src/boundaryconditions/BoundaryConditions.jl | 28 ++- src/stokes/Stokes2D.jl | 11 +- subduction/2D/LAMEM2D_sticky.jl | 63 +++++-- subduction/2D/LAMEM_rheology.jl | 170 +++---------------- subduction/2D/LAMEM_setup2D.jl | 1 - subduction/2D/LAMEM_setup2D_sticky.jl | 8 +- 6 files changed, 110 insertions(+), 171 deletions(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index 58951a62..b9da8749 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -253,7 +253,7 @@ end function free_surface_bcs!( stokes, bcs::FlowBoundaryConditions, η, rheology, phase_ratios, dt, di ) - indices_range(::Any, Vy) = @idx (size(Vy, 2) - 1) + indices_range(::Any, Vy) = @idx (size(Vy, 1) - 2) indices_range(::Any, ::Any, Vz) = @idx (size(Vz, 1) - 2, size(Vz, 2) - 2) V = @velocity(stokes) @@ -275,6 +275,32 @@ function free_surface_bcs!( end end +function free_surface_bcs!( + stokes, bcs::FlowBoundaryConditions, args::NamedTuple, η, rheology, phase_ratios, dt, di +) + indices_range(::Any, Vy) = @idx (size(Vy, 1) - 2) + indices_range(::Any, ::Any, Vz) = @idx (size(Vz, 1) - 2, size(Vz, 2) - 2) + + V = @velocity(stokes) + n = indices_range(V...) + + if bcs.free_surface + # apply boundary conditions + @parallel n FreeSurface_Vy!( + V..., + args.P, + stokes.P0, + stokes.τ_o.yy, + η, + rheology, + phase_ratios.center, + dt, + di..., + ) + end +end + + function free_surface_bcs!( stokes::StokesArrays{A,B,C,D,E,2}, bcs::FlowBoundaryConditions ) where {A,B,C,D,E} diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index d7fc5891..08004375 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -586,7 +586,6 @@ function JustRelax.solve!( if rem(iter, 5) == 0 @parallel (@idx ni) compute_ρg!(ρg[2], phase_ratios.center, rheology, args) end - # args.P .= reverse(cumsum(reverse(ρg[2] .+ ρg_bg, dims=2), dims=2), dims=2) @parallel (@idx ni .+ 1) compute_strain_rate!( @strain(stokes)..., stokes.∇V, @velocity(stokes)..., _di... @@ -625,11 +624,13 @@ function JustRelax.solve!( θ_dτ, ) # θ .-= args.P - free_surface_bcs!(stokes, flow_bcs) + # free_surface_bcs!(stokes, flow_bcs) @parallel center2vertex!(stokes.τ.xy, stokes.τ.xy_c) update_halo!(stokes.τ.xy) + # stokes.τ.yy[:, end] .= Plitho[:, end] + @parallel (1:(size(stokes.V.Vy, 1) - 2), 1:size(stokes.V.Vy, 2)) interp_Vx_on_Vy!( Vx_on_Vy, stokes.V.Vx ) @@ -641,14 +642,14 @@ function JustRelax.solve!( stokes.P, @stress(stokes)..., pt_stokes.ηdτ, - ρg[1], - ρg[2], + ρg..., ητ, _di..., dt * free_surface, ) # apply boundary conditions - free_surface_bcs!(stokes, flow_bcs, η, rheology, phase_ratios, dt, di) + free_surface_bcs!(stokes, flow_bcs, args, η, rheology, phase_ratios, dt, di) + # free_surface_bcs!(stokes, flow_bcs, η, rheology, phase_ratios, dt, di) flow_bcs!(stokes, flow_bcs) update_halo!(@velocity(stokes)...) end diff --git a/subduction/2D/LAMEM2D_sticky.jl b/subduction/2D/LAMEM2D_sticky.jl index 5d5b24f6..6e399311 100644 --- a/subduction/2D/LAMEM2D_sticky.jl +++ b/subduction/2D/LAMEM2D_sticky.jl @@ -1,21 +1,21 @@ -# using CUDA +using CUDA using JustRelax, JustRelax.DataIO import JustRelax.@cell using ParallelStencil -@init_parallel_stencil(Threads, Float64, 2) -# @init_parallel_stencil(CUDA, Float64, 2) +# @init_parallel_stencil(Threads, Float64, 2) +@init_parallel_stencil(CUDA, Float64, 2) using JustPIC using JustPIC._2D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend -# const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend # setup ParallelStencil.jl environment -model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) -# model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +# model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) +model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) environment!(model) # Load script dependencies @@ -24,6 +24,7 @@ using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays # Load file with all the rheology configurations include("LAMEM_rheology.jl") include("LAMEM_setup2D_sticky.jl") +# include("LAMEM_setup2D.jl") ## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- @@ -47,7 +48,6 @@ end @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) return nothing end - ## END OF HELPER FUNCTION ------------------------------------------------------------ ## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- @@ -61,9 +61,9 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # ---------------------------------------------------- # Physical properties using GeoParams ---------------- - rheology = init_rheologies() - rheology_augmented = init_augmented_rheologies() - dt = 1e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + rheology = init_rheologies(; ρbg = 2.7e3) + rheology_augmented = init_rheologies(; ρbg = 0e0) + dt = 50e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter # ---------------------------------------------------- # Initialize particles ------------------------------- @@ -117,9 +117,10 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Plitho = reverse(cumsum(reverse(ρg[2] .+ (2700*9.81), dims=2), dims=2), dims=2) ρg_bg = 2700 * 9.81 - # args.P .= reverse(cumsum(reverse(ρg[2] .+ ρg_bg, dims=2), dims=2), dims=2) + # Plitho = reverse(cumsum(reverse(ρg[2] .* di[2], dims=2), dims=2), dims=2) Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) - # args.P = stokes.P .+ Plitho .- minimum(stokes.P) + # Plitho -= stokes.P .+ Plitho .- minimum(stokes.P) + # stokes.P .= Plitho # Rheology η = @ones(ni...) @@ -197,13 +198,13 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk thermal_bcs!(thermal, thermal_bc) temperature2center!(thermal) - # Update buoyancy and viscosity - + # # Update buoyancy and viscosity - Plitho .= reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) - # Plitho .= -(ρg[2] .+ ρg_bg) .* xci[2]' + # # Plitho .= -(ρg[2] .+ ρg_bg) .* xci[2]' Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) - # args.P .= 0 args = (; T = thermal.Tc, P = Plitho, dt=Inf) + # args = (; T = thermal.Tc, P = stokes.P, dt=Inf) compute_viscosity!( η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) ) @@ -224,8 +225,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk args, dt, igg; - iterMax = 100e3, - nout = 2e3, + iterMax = 150e3, + nout = 1e3, viscosity_cutoff = (1e18, 1e24), free_surface = false, viscosity_relaxation = 1e-2 @@ -360,6 +361,7 @@ figdir = "Subduction_LAMEM_2D" n = 64 nx, ny = n*6, n nx, ny = 512, 128 +nx, ny = 512, 132 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) @@ -368,3 +370,28 @@ else end # main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); + +# f,ax,h=heatmap(xvi./1e3..., phases_GMG) +# heatmap(xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η)) , colormap=:lipari, colorrange=(18, 24)) +# scatter(Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv]), markersize=2) + +# p = [argmax(p) for p in phase_ratios.center] + +# lines(log10.(η[100,:]), xci[2]) +# lines(Plitho[100,:], xci[2]) +# lines!(Plitho[100,:], xci[2]) +# heatmap(log10.(η), xci[1]./1e3, xci[2]./1e3, colormap=:batlow) +# heatmap(xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η)) , colormap=:lipari, colorrange=(18, 24)) + +# flow_bcs = FlowBoundaryConditions(; +# free_slip = (left = true , right = true , top = true , bot = true), +# free_surface = true, +# ) + +# free_surface_bcs!( +# stokes, flow_bcs, η, rheology, phase_ratios, dt, di +# ) + +# size(stokes.V.Vy, 1) - 2 + +# free_surface_bcs!(stokes, flow_bcs, args, η, rheology, phase_ratios, dt, di) \ No newline at end of file diff --git a/subduction/2D/LAMEM_rheology.jl b/subduction/2D/LAMEM_rheology.jl index 67785e65..b2d427e6 100644 --- a/subduction/2D/LAMEM_rheology.jl +++ b/subduction/2D/LAMEM_rheology.jl @@ -1,7 +1,7 @@ using GeoParams.Dislocation using GeoParams.Diffusion -function init_rheologies() +function init_rheologies(; ρbg = 0e0) disl_dry_olivine = SetDislocationCreep(Dislocation.dry_olivine_Hirth_2003; V = 14.5e-6) disl_oceanic_crust = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) # disl_oceanic_litho = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) @@ -10,25 +10,26 @@ function init_rheologies() diff_dry_olivine = SetDiffusionCreep(Diffusion.dry_olivine_Hirth_2003; V = 14.5e-6) ϕ_dry_olivine = sind(20) - C_dry_olivine = 30e6 + C_dry_olivine = 30e6 * Inf ϕ_oceanic_crust = sind(0) - C_oceanic_crust = 5e6 + C_oceanic_crust = 5e6 * Inf ϕ_oceanic_litho = sind(10) - C_oceanic_litho = 5e6 + C_oceanic_litho = 5e6 * Inf ϕ_cont_crust = sind(20) - C_cont_crust = 30e6 + C_cont_crust = 30e6 * Inf soft_C = LinearSoftening((C_oceanic_litho*0.05, C_oceanic_litho), (0.1, 0.5)) + elasticity = ConstantElasticity(; G=5e10, ν=0.4) # common physical properties α = 3e-5 # 1 / K Cp = 1000 # J / kg K # C = 3e6 # Pa η_reg = 1e20 - ρbg = 2700 # kg / m^3 + # ρbg = 2700 # kg / m^3 # Define rheolgy struct rheology = ( @@ -42,12 +43,12 @@ function init_rheologies() ( disl_dry_olivine, diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), + elasticity, DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Elasticity = elasticity, Gravity = ConstantGravity(; g=9.81), ), # Name = "oceanic crust", @@ -59,7 +60,7 @@ function init_rheologies() CompositeRheology = CompositeRheology( ( disl_oceanic_crust, - ConstantElasticity(; G=5e10, ν=0.5), + elasticity, DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), @@ -75,12 +76,12 @@ function init_rheologies() ( disl_dry_olivine, diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), + elasticity, DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Elasticity = elasticity, ), # Name = "continental crust", SetMaterialParams(; @@ -92,11 +93,11 @@ function init_rheologies() CompositeRheology = CompositeRheology( ( disl_cont_crust, - ConstantElasticity(; G=5e10, ν=0.5), + elasticity, DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Elasticity = elasticity, ), # Name = "continental lithosphere", SetMaterialParams(; @@ -108,147 +109,27 @@ function init_rheologies() ( disl_dry_olivine, diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), + elasticity, DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity ) ), RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), + Elasticity = elasticity, ), # Name = "StickyAir", SetMaterialParams(; Phase = 6, - Density = ConstantDensity(; ρ=1e0-ρbg), # water density + Density = ConstantDensity(; ρ=2-ρbg), # water density HeatCapacity = ConstantHeatCapacity(; Cp=3e3), RadioactiveHeat = ConstantRadioactiveHeat(0.0), - Conductivity = ConstantConductivity(; k=1.0), - CompositeRheology = CompositeRheology((LinearViscous(; η=1e18),)), - ), - ) -end - -function init_augmented_rheologies() - disl_dry_olivine = SetDislocationCreep(Dislocation.dry_olivine_Hirth_2003; V = 14.5e-6) - disl_oceanic_crust = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) - # disl_oceanic_litho = SetDislocationCreep(Dislocation.plagioclase_An75_Ji_1993) - disl_cont_crust = SetDislocationCreep(Dislocation.wet_quartzite_Kirby_1983) - - diff_dry_olivine = SetDiffusionCreep(Diffusion.dry_olivine_Hirth_2003; V = 14.5e-6) - - ϕ_dry_olivine = sind(20) - C_dry_olivine = 30e6 - - ϕ_oceanic_crust = sind(0) - C_oceanic_crust = 5e6 - - ϕ_oceanic_litho = sind(10) - C_oceanic_litho = 5e6 - - ϕ_cont_crust = sind(20) - C_cont_crust = 30e6 - - soft_C = LinearSoftening((C_oceanic_litho*0.05, C_oceanic_litho), (0.1, 0.5)) - - # common physical properties - α = 3e-5 # 1 / K - Cp = 1000 # J / kg K - # C = 3e6 # Pa - η_reg = 1e20 - ρbg = 0 # kg / m^3 - - # Define rheolgy struct - rheology = ( - # Name = "dry olivine - Hirth_Kohlstedt_2003", - SetMaterialParams(; - Phase = 1, - Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), - HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k = 3), - CompositeRheology = CompositeRheology( - ( - disl_dry_olivine, - diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), - Gravity = ConstantGravity(; g=9.81), - ), - # Name = "oceanic crust", - SetMaterialParams(; - Phase = 2, - Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), - HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k =3 ), - CompositeRheology = CompositeRheology( + Conductivity = ConstantConductivity(; k=3.0), + CompositeRheology = CompositeRheology( ( - disl_oceanic_crust, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_oceanic_crust, ϕ = ϕ_oceanic_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) + LinearViscous(; η=1e24), + # elasticity + ) ), - RadioactiveHeat = ConstantRadioactiveHeat(2.333e-10), - ), - # Name = "oceanic lithosphere", - SetMaterialParams(; - Phase = 3, - Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), - HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k = 3), - CompositeRheology = CompositeRheology( - ( - disl_dry_olivine, - diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), - ), - # Name = "continental crust", - SetMaterialParams(; - Phase = 4, - Density = PT_Density(; ρ0=2.7e3-ρbg, α = α, β = 0e0, T0 = 273), - RadioactiveHeat = ConstantRadioactiveHeat(5.3571e-10), - HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k =3 ), - CompositeRheology = CompositeRheology( - ( - disl_cont_crust, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_cont_crust, ϕ = ϕ_cont_crust, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) - ), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), - ), - # Name = "continental lithosphere", - SetMaterialParams(; - Phase = 5, - Density = PT_Density(; ρ0=3.3e3-ρbg, α = α, β = 0e0, T0 = 273), - HeatCapacity = ConstantHeatCapacity(; Cp=Cp), - Conductivity = ConstantConductivity(; k = 3), - CompositeRheology = CompositeRheology( - ( - disl_dry_olivine, - diff_dry_olivine, - ConstantElasticity(; G=5e10, ν=0.5), - DruckerPrager_regularised(; C = C_dry_olivine, ϕ=ϕ_dry_olivine, η_vp=η_reg, Ψ=0.0, softening_C = soft_C) # non-regularized plasticity - ) - ), - RadioactiveHeat = ConstantRadioactiveHeat(6.6667e-12), - Elasticity = ConstantElasticity(; G=5e10, ν=0.5), - ), - # Name = "StickyAir", - SetMaterialParams(; - Phase = 6, - Density = ConstantDensity(; ρ=1e0-ρbg), # water density - HeatCapacity = ConstantHeatCapacity(; Cp=3e3), - RadioactiveHeat = ConstantRadioactiveHeat(0.0), - Conductivity = ConstantConductivity(; k=1.0), - CompositeRheology = CompositeRheology((LinearViscous(; η=1e18),)), + # Elasticity = elasticity, ), ) end @@ -290,6 +171,11 @@ end end end @cell phases[ip, I...] = Float64(particle_phase) + + if pᵢ[2] ≥ 0.0 + @cell phases[ip, I...] = 6.0 + end + end return nothing diff --git a/subduction/2D/LAMEM_setup2D.jl b/subduction/2D/LAMEM_setup2D.jl index 5a3eeda6..3001a536 100644 --- a/subduction/2D/LAMEM_setup2D.jl +++ b/subduction/2D/LAMEM_setup2D.jl @@ -1,6 +1,5 @@ using GeophysicalModelGenerator - function GMG_subduction_2D(nx, ny) # Our starting basis is the example above with ridge and overriding slab nx, nz = nx, ny diff --git a/subduction/2D/LAMEM_setup2D_sticky.jl b/subduction/2D/LAMEM_setup2D_sticky.jl index b44b09bd..beaa454f 100644 --- a/subduction/2D/LAMEM_setup2D_sticky.jl +++ b/subduction/2D/LAMEM_setup2D_sticky.jl @@ -9,7 +9,7 @@ function GMG_subduction_2D(nx, ny) Phases = zeros(Int64, nx, 1, nz); Temp = fill(1280.0, nx, 1, nz); air_thickness = 20.0 - lith = LithosphericPhases(Layers=[air_thickness+20 80], Phases=[1 2 0]) + lith = LithosphericPhases(Layers=[20 80], Phases=[1 2 0]) # Add left oceanic plate add_box!( @@ -57,7 +57,7 @@ function GMG_subduction_2D(nx, ny) xlim =(0, 400), zlim =(-660.0, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, - phase = LithosphericPhases(Layers=[air_thickness+25 90], Phases=[3 4 0] ), + phase = LithosphericPhases(Layers=[25 90], Phases=[3 4 0] ), T = HalfspaceCoolingTemp( Tsurface = 20, Tmantle = 1280.0, @@ -73,7 +73,7 @@ function GMG_subduction_2D(nx, ny) xlim =(400, 1500), zlim =(-660.0, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, - phase = LithosphericPhases(Layers=[air_thickness+35 100], Phases=[3 4 0] ), + phase = LithosphericPhases(Layers=[35 100], Phases=[3 4 0] ), T = HalfspaceCoolingTemp( Tsurface = 20, Tmantle = 1280.0, @@ -88,7 +88,7 @@ function GMG_subduction_2D(nx, ny) xlim =(0, 300), zlim =(-660.0, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=30, - phase = LithosphericPhases(Layers=[air_thickness+30 80], Phases=[1 2 0], Tlab=1250 ), + phase = LithosphericPhases(Layers=[30 80], Phases=[1 2 0], Tlab=1250 ), T = HalfspaceCoolingTemp( Tsurface = 20, Tmantle = 1280.0, From ad9e9a75d661a46b581a90774c8aa8b5dde96bca Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Fri, 26 Apr 2024 17:49:24 +0200 Subject: [PATCH 37/60] internal changes --- src/MetaJustRelax.jl | 1 + src/stokes/MetaStokes.jl | 89 +++++++++++++--- src/stokes/Stokes2D.jl | 21 +++- src/stokes/Stokes3D.jl | 4 +- src/stokes/StressRotation.jl | 1 - src/stokes/StressRotationParticles.jl | 146 ++++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 20 deletions(-) create mode 100644 src/stokes/StressRotationParticles.jl diff --git a/src/MetaJustRelax.jl b/src/MetaJustRelax.jl index f527d099..7edf21e8 100644 --- a/src/MetaJustRelax.jl +++ b/src/MetaJustRelax.jl @@ -50,6 +50,7 @@ function environment!(model::PS_Setup{T,N}) where {T,N} # CREATE ARRAY STRUCTS make_velocity_struct!(N) # velocity make_symmetrictensor_struct!(N) # (symmetric) tensors + make_vorticity_struct!(N) # (symmetric) tensors ## Stokes make_residual_struct!(N) # residuals make_stokes_struct!() # Arrays for Stokes solver diff --git a/src/stokes/MetaStokes.jl b/src/stokes/MetaStokes.jl index f7f76c25..8438074a 100644 --- a/src/stokes/MetaStokes.jl +++ b/src/stokes/MetaStokes.jl @@ -20,8 +20,60 @@ function make_velocity_struct!(ndim::Integer; name::Symbol=:Velocity) return new{$PTArray}(@zeros(ni[1]...), @zeros(ni[2]...), @zeros(ni[3]...)) end - $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = new{$PTArray}(args...) end + $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = $(name)(args...) + end +end + +abstract type AbstractVorticityTensor end + +function make_vorticity_struct!(ndim::Integer) + dims = (:xy, :xz, :yz) + fields_c = if ndim == 2 + [:($(Symbol(dims[1], :_c))::T)] + + elseif ndim == 3 + [:($(Symbol(dims[i], :_c))::T) for i in 1:ndim] + else + throw(ArgumentError("ndim must be 2 or 3")) + end + + fields_v = if ndim == 2 + [:($(Symbol(dims[1], :_v))::T)] + + elseif ndim == 3 + [:($(Symbol(dims[i], :_v))::T) for i in 1:ndim] + end + + @eval begin + struct VorticityTensor{T} <: AbstractVorticityTensor + $(fields_c...) # centers + $(fields_v...) # vertices + + function VorticityTensor(ni::NTuple{2,T}) where {T} + # centers + xy_c = @zeros(ni...) + # vertices + xy_v = @zeros(ni .+ 1...) + + return new{$PTArray}(xy_c, xy_v) + end + + function VorticityTensor(ni::NTuple{3,T}) where {T} + # centers + yz_c = @zeros(ni...) + xz_c = @zeros(ni...) + xy_c = @zeros(ni...) + # vertices + yz_v = @zeros(ni .+ 1...) + xz_v = @zeros(ni .+ 1...) + xy_v = @zeros(ni .+ 1...) + + return new{$PTArray}(yz_c, xz_c, xy_c, yz_v, xz_v, xy_v) + end + + end + VorticityTensor(args::Vararg{T,N}) where {T<:AbstractArray,N} = VorticityTensor(args...) end end @@ -83,8 +135,9 @@ function make_symmetrictensor_struct!(nDim::Integer; name::Symbol=:SymmetricTens ) end - $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = new{$PTArray}(args...) end + + $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = $(name)(args...) end end @@ -111,19 +164,21 @@ function make_residual_struct!(ndim; name::Symbol=:Residual) return new{typeof(Rx)}(Rx, Ry, Rz, RP) end - $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = new{$PTArray}(args...) end + $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = $(name)(args...) end end function make_stokes_struct!(; name::Symbol=:StokesArrays) @eval begin - struct StokesArrays{M<:AbstractStokesModel,A,B,C,T,nDim} + struct StokesArrays{M<:AbstractStokesModel,A,B,C,D,T,nDim} P::T P0::T V::A ∇V::T τ::B + ω::D + ω_o::D ε::B ε_pl::B EII_pl::T @@ -139,12 +194,14 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) V = Velocity(((ni[1] + 1, ni[2] + 2), (ni[1], ni[2] + 2))) τ = SymmetricTensor(ni) ε = SymmetricTensor(ni) + ω = VorticityTensor(ni) # vorticity + ω_o = VorticityTensor(ni) # vorticity ε_pl = SymmetricTensor(ni) EII_pl = @zeros(ni...) R = Residual(((ni[1] - 1, ni[2]), (ni[1], ni[2] - 1)), ni) - return new{model,typeof(V),typeof(τ),typeof(R),typeof(P),2}( - P, P0, V, ∇V, τ, ε, ε_pl, EII_pl, nothing, R + return new{model,typeof(V),typeof(τ),typeof(R),typeof(ω),typeof(P),2}( + P, P0, V, ∇V, τ, ω, ω_o, ε, ε_pl, EII_pl, nothing, R ) end @@ -157,12 +214,14 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) V = Velocity(((ni[1] + 1, ni[2] + 2), (ni[1] + 2, ni[2] + 1))) τ = SymmetricTensor(ni) ε = SymmetricTensor(ni) + ω = VorticityTensor(ni) # vorticity + ω_o = VorticityTensor(ni) # vorticity ε_pl = SymmetricTensor(ni) EII_pl = @zeros(ni...) R = Residual(((ni[1] - 1, ni[2]), (ni[1], ni[2] - 1), ni)) - return new{model,typeof(V),typeof(τ),typeof(R),typeof(P),2}( - P, P0, V, ∇V, τ, ε, ε_pl, EII_pl, deepcopy(τ), R + return new{model,typeof(V),typeof(τ),typeof(R),typeof(ω),typeof(P),2}( + P, P0, V, ∇V, τ, ω, ω_o, ε, ε_pl, EII_pl, deepcopy(τ), R ) end @@ -179,6 +238,8 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) )) τ = SymmetricTensor(ni) ε = SymmetricTensor(ni) + ω = VorticityTensor(ni) # vorticity + ω_o = VorticityTensor(ni) # vorticity ε_pl = SymmetricTensor(ni) EII_pl = @zeros(ni...) R = Residual(( @@ -188,8 +249,8 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) ni, )) - return new{model,typeof(V),typeof(τ),typeof(R),typeof(P),3}( - P, P0, V, ∇V, τ, ε, ε_pl, EII_pl, nothing, R + return new{model,typeof(V),typeof(τ),typeof(R),typeof(ω),typeof(P),3}( + P, P0, V, ∇V, τ, ω, ω_o, ε, ε_pl, EII_pl, nothing, R ) end @@ -206,6 +267,8 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) )) τ = SymmetricTensor(ni) ε = SymmetricTensor(ni) + ω = VorticityTensor(ni) # vorticity + ω_o = VorticityTensor(ni) # vorticity ε_pl = SymmetricTensor(ni) EII_pl = @zeros(ni...) R = Residual(( @@ -215,8 +278,8 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) ni, )) - return new{model,typeof(V),typeof(τ),typeof(R),typeof(P),3}( - P, P0, V, ∇V, τ, ε, ε_pl, EII_pl, deepcopy(τ), R + return new{model,typeof(V),typeof(τ),typeof(R),typeof(ω),typeof(P),3}( + P, P0, V, ∇V, τ, ω, ω_o, ε, ε_pl, EII_pl, deepcopy(τ), R ) end @@ -226,12 +289,14 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) typeof(args[4]), typeof(args[5]), typeof(args[end]), + typeof(args[6]), typeof(args[1]), N, }( args... ) end + end end end diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 08004375..2c90f3c8 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -43,6 +43,9 @@ using CUDA, AMDGPU using ParallelStencil # using ParallelStencil.FiniteDifferences2D using GeoParams, LinearAlgebra, Printf +using JustPIC, JustPIC._2D + +import JustPIC._2D: @cell import JustRelax: elastic_iter_params!, PTArray, Velocity, SymmetricTensor import JustRelax: @@ -53,7 +56,8 @@ import JustRelax: mean_mpi, norm_mpi, maximum_mpi, minimum_mpi, backend @eval @init_parallel_stencil($backend, Float64, 2) include("../rheology/GeoParams.jl") -include("StressRotation.jl") +# include("StressRotation.jl") +include("StressRotationParticles.jl") include("StressKernels.jl") include("PressureKernels.jl") include("VelocityKernels.jl") @@ -489,7 +493,7 @@ end ## With phase ratios function JustRelax.solve!( - stokes::StokesArrays{ViscoElastic,A,B,C,D,2}, + stokes::StokesArrays{ViscoElastic,A,B,C,D,E,2}, pt_stokes::PTStokesCoeffs, di::NTuple{2,T}, flow_bcs, @@ -509,7 +513,7 @@ function JustRelax.solve!( nout=500, b_width=(4, 4, 0), verbose=true, -) where {A,B,C,D,T} +) where {A,B,C,D,E,T} # unpack @@ -708,6 +712,12 @@ function JustRelax.solve!( # accumulate plastic strain tensor @parallel (@idx ni) accumulate_tensor!(stokes.EII_pl, @tensor_center(stokes.ε_pl), dt) + compute_vorticity!(stokes, di) + + # @parallel (@idx ni .+ 1) multi_copy!(@tensor(stokes.τ_o), @tensor(stokes.τ)) + # @parallel (@idx ni) multi_copy!( + # @tensor_center(stokes.τ_o), @tensor_center(stokes.τ) + # ) return ( iter=iter, @@ -717,10 +727,11 @@ function JustRelax.solve!( norm_Ry=norm_Ry, norm_∇V=norm_∇V, ) + end function JustRelax.solve!( - stokes::StokesArrays{ViscoElastic,A,B,C,D,2}, + stokes::StokesArrays{ViscoElastic,A,B,C,D,E,2}, thermal::ThermalArrays, pt_stokes::PTStokesCoeffs, di::NTuple{2,T}, @@ -739,7 +750,7 @@ function JustRelax.solve!( nout=500, b_width=(4, 4, 1), verbose=true, -) where {A,B,C,D,N,T} +) where {A,B,C,D,E,N,T} # unpack diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index 1a436268..d3b59945 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -372,7 +372,7 @@ end # GeoParams and multiple phases function JustRelax.solve!( - stokes::StokesArrays{ViscoElastic,A,B,C,D,3}, + stokes::StokesArrays{ViscoElastic,A,B,C,D,E,3}, pt_stokes::PTStokesCoeffs, di::NTuple{3,T}, flow_bc::FlowBoundaryConditions, @@ -389,7 +389,7 @@ function JustRelax.solve!( b_width=(4, 4, 4), verbose=true, viscosity_cutoff=(-Inf, Inf), -) where {A,B,C,D,T,N} +) where {A,B,C,D,E,T,N} ## UNPACK diff --git a/src/stokes/StressRotation.jl b/src/stokes/StressRotation.jl index ea9b45da..777e5c03 100644 --- a/src/stokes/StressRotation.jl +++ b/src/stokes/StressRotation.jl @@ -96,7 +96,6 @@ end Jaumann derivative τij_o += v_k * ∂τij_o/∂x_k - ω_ij * ∂τkj_o + ∂τkj_o * ω_ij - """ Base.@propagate_inbounds function rotate_stress!( V, τ::NTuple{N,T}, idx, _di, dt diff --git a/src/stokes/StressRotationParticles.jl b/src/stokes/StressRotationParticles.jl new file mode 100644 index 00000000..828026c6 --- /dev/null +++ b/src/stokes/StressRotationParticles.jl @@ -0,0 +1,146 @@ +using StaticArrays + +# ROTATION KERNELS + +## Stress Rotation on the particles +function compute_vorticity!(stokes::StokesArrays, di::NTuple{2}) + ωxy = stokes.ω.xy_c + ni = size(ωxy) + @parallel (@idx ni) compute_vorticity!(ωxy, @velocity(stokes)..., inv.(di)...) + + return nothing +end + +@parallel_indices (i, j) function compute_vorticity!(ωxy, Vx, Vy, _dx, _dy) + dx(A) = _d_xa(A, i, j, _dx) + dy(A) = _d_ya(A, i, j, _dy) + + ωxy[i, j] = 0.5 * (dx(Vy) - dy(Vx)) + + return nothing +end + +function jaumann!(xx, yy, xy, ω, index, dt) + ni = size(xx) + @parallel (@idx ni) _jaumann!(xx, yy, xy, ω, index, dt) + return nothing +end + +@parallel_indices (i, j) function _jaumann!(xx, yy, xy, ω, index, dt) + cell = i, j + + for ip in JustRelax.cellaxes(index) + !@cell(index[ip, cell...]) && continue # no particle in this location + + ω_xy = @cell ω[ip, cell...] + τ_xx = @cell xx[ip, cell...] + τ_yy = @cell yy[ip, cell...] + τ_xy = @cell xy[ip, cell...] + + tmp = τ_xy * ω_xy * 2.0 + @cell xx[ip, cell...] = muladd(dt, tmp, τ_xx) + @cell yy[ip, cell...] = muladd(dt, tmp, τ_yy) + @cell xy[ip, cell...] = muladd(dt, (τ_xx - τ_yy) * ω_xy, τ_xy) + end + + return nothing +end + +function rotation_matrix!(xx, yy, xy, ω, index, dt) + ni = size(xx) + @parallel (@idx ni) _rotation_matrix!(xx, yy, xy, ω, index, dt) + return nothing +end + +@parallel_indices (i, j) function _rotation_matrix!( + xx, yy, xy, ω, index, dt +) + cell = i, j + + for ip in JustRelax.cellaxes(index) + !@cell(index[ip, cell...]) && continue # no particle in this location + + θ = dt * @cell ω[ip, cell...] + sinθ, cosθ = sincos(θ) + + τ_xx = @cell xx[ip, cell...] + τ_yy = @cell yy[ip, cell...] + τ_xy = @cell xy[ip, cell...] + + R = @SMatrix [ + cosθ -sinθ + sinθ cosθ + ] + + τ = @SMatrix [ + τ_xx τ_xy + τ_xy τ_yy + ] + + # this could be fully unrolled in 2D + τr = R * τ * R' + + @cell xx[ip, cell...] = τr[1, 1] + @cell yy[ip, cell...] = τr[2, 2] + @cell xy[ip, cell...] = τr[1, 2] + end + + return nothing +end + +# STRESS ROTATION ON THE PARTICLES + +function rotate_stress_particles!( + stokes::StokesArrays, + τxx_vertex::T, + τyy_vertex::T, + τxx_o_vertex::T, + τyy_o_vertex::T, + τxx_p::CA, + τyy_p::CA, + τxy_p::CA, + vorticity_p::CA, + particles, + grid::Geometry, + dt; + fn = rotation_matrix! +) where {T<:AbstractArray, CA} + (;xvi, xci) = grid + nx, ny = size(τxx_p) + + # interpolate stresses to particles + for (src, src_o, dst_v, dst_v_o, dst_p) in zip( + @tensor_center(stokes.τ), + @tensor_center(stokes.τ_o), + (τxx_vertex, τyy_vertex, stokes.τ.xy), + (τxx_o_vertex, τyy_o_vertex, stokes.τ_o.xy), + (τxx_p, τyy_p, τxy_p) + ) + @parallel center2vertex!(dst_v, src) + @parallel center2vertex!(dst_v_o, src_o) + @parallel (1:nx + 1) free_slip_y!(dst_v) + @parallel (1:ny + 1) free_slip_x!(dst_v) + @parallel (1:nx + 1) free_slip_y!(dst_v_o) + @parallel (1:ny + 1) free_slip_x!(dst_v_o) + grid2particle_flip!(dst_p, xvi, dst_v, dst_v_o, particles) + end + + # interpolate vorticity to particles + @parallel center2vertex!(stokes.ω.xy_v, stokes.ω.xy_c) + @parallel center2vertex!(stokes.ω_o.xy_v, stokes.ω_o.xy_c) + @parallel (1:nx) free_slip_y!(stokes.ω.xy_c) + @parallel (1:ny) free_slip_x!(stokes.ω.xy_c) + @parallel (1:nx+1) free_slip_y!(stokes.ω_o.xy_v) + @parallel (1:ny+1) free_slip_x!(stokes.ω_o.xy_v) + grid2particle_flip!(vorticity_p, xvi, stokes.ω.xy_v, stokes.ω_o.xy_v, particles) + # rotate old stress + fn( + τxx_p, τyy_p, τxy_p, vorticity_p, particles.index, dt + ) + # interpolate old stress to grid arrays + particle2grid_centroid!(stokes.τ_o.xx, τxx_p, xci, particles) + particle2grid_centroid!(stokes.τ_o.yy, τyy_p, xci, particles) + particle2grid_centroid!(stokes.τ_o.xy_c, τxy_p, xci, particles) +end + + From 2c6a49582fdcc9ad5b7e3c83b8b637496c7056f4 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Fri, 26 Apr 2024 17:49:33 +0200 Subject: [PATCH 38/60] update scripts --- subduction/2D/LAMEM2D_sticky.jl | 121 +++++++++++++++----------- subduction/2D/LAMEM_rheology.jl | 12 +-- subduction/2D/LAMEM_setup2D_sticky.jl | 31 +++++-- 3 files changed, 103 insertions(+), 61 deletions(-) diff --git a/subduction/2D/LAMEM2D_sticky.jl b/subduction/2D/LAMEM2D_sticky.jl index 6e399311..cb80d741 100644 --- a/subduction/2D/LAMEM2D_sticky.jl +++ b/subduction/2D/LAMEM2D_sticky.jl @@ -1,22 +1,38 @@ -using CUDA +const isCUDA = true + +@static if isCUDA + using CUDA +end + using JustRelax, JustRelax.DataIO import JustRelax.@cell using ParallelStencil -# @init_parallel_stencil(Threads, Float64, 2) -@init_parallel_stencil(CUDA, Float64, 2) +@static if isCUDA + @init_parallel_stencil(CUDA, Float64, 2) +else + @init_parallel_stencil(Threads, Float64, 2) +end using JustPIC using JustPIC._2D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -# const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend -const backend = CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = @static if isCUDA + CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +else + CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +end # setup ParallelStencil.jl environment -# model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) -model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) -environment!(model) + +@static if isCUDA + model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) + environment!(model) +else + model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) + environment!(model) +end # Load script dependencies using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays @@ -61,7 +77,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # ---------------------------------------------------- # Physical properties using GeoParams ---------------- - rheology = init_rheologies(; ρbg = 2.7e3) + ρbg = 2.7e3 + rheology = init_rheologies(; ρbg = ρbg) rheology_augmented = init_rheologies(; ρbg = 0e0) dt = 50e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter # ---------------------------------------------------- @@ -77,9 +94,11 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # velocity grids grid_vxi = velocity_grids(xci, xvi, di) # temperature - pPhases, pT = init_cell_arrays(particles, Val(2)) - particle_args = (pPhases, pT) - + pPhases, pT = init_cell_arrays(particles, Val(2)) + τxx_p, τyy_p, τxy_p = init_cell_arrays(particles, Val(3)) + vorticity_p, = init_cell_arrays(particles, Val(1)) + particle_args = (pT, τxx_p, τyy_p, τxy_p, vorticity_p, pPhases) + # Assign particles phases anomaly phases_device = PTArray(phases_GMG) init_phases!(pPhases, phases_device, particles, xvi) @@ -114,13 +133,9 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) # end compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) - # Plitho = reverse(cumsum(reverse(ρg[2] .+ (2700*9.81), dims=2), dims=2), dims=2) - ρg_bg = 2700 * 9.81 - # Plitho = reverse(cumsum(reverse(ρg[2] .* di[2], dims=2), dims=2), dims=2) + ρg_bg = ρbg * 9.81 Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) - # Plitho -= stokes.P .+ Plitho .- minimum(stokes.P) - # stokes.P .= Plitho # Rheology η = @ones(ni...) @@ -187,6 +202,10 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Time loop t, it = 0.0, 0 + # Vertice arrays of normal components of the stress tensor + τxx_vertex, τyy_vertex = @zeros(ni.+1...), @zeros(ni.+1...) + τxx_o_vertex, τyy_o_vertex = @zeros(ni.+1...), @zeros(ni.+1...) + while it < 1000 # run only for 5 Myrs # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs @@ -200,7 +219,6 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # # Update buoyancy and viscosity - Plitho .= reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) - # # Plitho .= -(ρg[2] .+ ρg_bg) .* xci[2]' Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) args = (; T = thermal.Tc, P = Plitho, dt=Inf) @@ -225,7 +243,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk args, dt, igg; - iterMax = 150e3, + iterMax = 10e3, nout = 1e3, viscosity_cutoff = (1e18, 1e24), free_surface = false, @@ -250,12 +268,12 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk di; igg = igg, phase = phase_ratios, - iterMax = 10e3, - nout = 1e2, + iterMax = 150e3, + nout = 2e3, verbose = true, ) subgrid_characteristic_time!( - subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + subgrid_arrays, particles, dt₀, phase_ratios, rheology_augmented, thermal, stokes, xci, di ) centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) subgrid_diffusion!( @@ -268,8 +286,23 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) # advect particles in memory move_particles!(particles, xvi, particle_args) + # JustRelax.Stokes2D.rotate_stress_particles!( + # stokes, + # τxx_vertex, + # τyy_vertex, + # τxx_o_vertex, + # τyy_o_vertex, + # τxx_p, + # τyy_p, + # τxy_p, + # vorticity_p, + # particles, + # grid, + # dt + # ) + # check if we need to inject particles - inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer,), xvi) + inject_particles_phase!(particles, pPhases, (pT, τxx_p, τyy_p, τxy_p, vorticity_p), (T_buffer, τxx_vertex, τyy_vertex, stokes.τ.xy, stokes.ω.xy_v), xvi) # update phase ratios @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) @@ -330,7 +363,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Plot temperature h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) # Plot particles phase - h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv]), markersize = 1) # Plot 2nd invariant of strain rate h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) # Plot effective viscosity @@ -343,8 +376,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk Colorbar(fig[1,4], h3) Colorbar(fig[2,4], h4) linkaxes!(ax1, ax2, ax3, ax4) - save(joinpath(figdir, "$(it).png"), fig) fig + save(joinpath(figdir, "$(it).png"), fig) end # ------------------------------ @@ -358,10 +391,10 @@ do_vtk = true # set to true to generate VTK files for ParaView figdir = "Subduction_LAMEM_2D" # nx, ny = 512, 256 # nx, ny = 512, 128 -n = 64 +n = 128 nx, ny = n*6, n nx, ny = 512, 128 -nx, ny = 512, 132 +# nx, ny = 512, 132 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) @@ -369,29 +402,19 @@ else igg end -# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); - -# f,ax,h=heatmap(xvi./1e3..., phases_GMG) -# heatmap(xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η)) , colormap=:lipari, colorrange=(18, 24)) -# scatter(Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv]), markersize=2) - -# p = [argmax(p) for p in phase_ratios.center] - -# lines(log10.(η[100,:]), xci[2]) -# lines(Plitho[100,:], xci[2]) -# lines!(Plitho[100,:], xci[2]) -# heatmap(log10.(η), xci[1]./1e3, xci[2]./1e3, colormap=:batlow) -# heatmap(xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η)) , colormap=:lipari, colorrange=(18, 24)) +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); -# flow_bcs = FlowBoundaryConditions(; -# free_slip = (left = true , right = true , top = true , bot = true), -# free_surface = true, -# ) +# c = Array(phase_ratios.center) +# a = [any(isnan, x) for x in c] -# free_surface_bcs!( -# stokes, flow_bcs, η, rheology, phase_ratios, dt, di -# ) +# any(isnan, c[1]) -# size(stokes.V.Vy, 1) - 2 +# p = particles.coords +# ppx, ppy = p +# pxv = ppx.data[:]./1e3 +# pyv = ppy.data[:]./1e3 +# clr = pPhases.data[:] +# # clr = pT.data[:] +# idxv = particles.index.data[:]; +# scatter!(Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv]), markersize = 3) -# free_surface_bcs!(stokes, flow_bcs, args, η, rheology, phase_ratios, dt, di) \ No newline at end of file diff --git a/subduction/2D/LAMEM_rheology.jl b/subduction/2D/LAMEM_rheology.jl index b2d427e6..85571a5c 100644 --- a/subduction/2D/LAMEM_rheology.jl +++ b/subduction/2D/LAMEM_rheology.jl @@ -10,16 +10,16 @@ function init_rheologies(; ρbg = 0e0) diff_dry_olivine = SetDiffusionCreep(Diffusion.dry_olivine_Hirth_2003; V = 14.5e-6) ϕ_dry_olivine = sind(20) - C_dry_olivine = 30e6 * Inf + C_dry_olivine = 30e6 ϕ_oceanic_crust = sind(0) - C_oceanic_crust = 5e6 * Inf + C_oceanic_crust = 5e6 ϕ_oceanic_litho = sind(10) - C_oceanic_litho = 5e6 * Inf + C_oceanic_litho = 5e6 ϕ_cont_crust = sind(20) - C_cont_crust = 30e6 * Inf + C_cont_crust = 30e6 soft_C = LinearSoftening((C_oceanic_litho*0.05, C_oceanic_litho), (0.1, 0.5)) @@ -119,13 +119,13 @@ function init_rheologies(; ρbg = 0e0) # Name = "StickyAir", SetMaterialParams(; Phase = 6, - Density = ConstantDensity(; ρ=2-ρbg), # water density + Density = ConstantDensity(; ρ=1-ρbg), # water density HeatCapacity = ConstantHeatCapacity(; Cp=3e3), RadioactiveHeat = ConstantRadioactiveHeat(0.0), Conductivity = ConstantConductivity(; k=3.0), CompositeRheology = CompositeRheology( ( - LinearViscous(; η=1e24), + LinearViscous(; η=1e21), # elasticity ) ), diff --git a/subduction/2D/LAMEM_setup2D_sticky.jl b/subduction/2D/LAMEM_setup2D_sticky.jl index beaa454f..1d44f9b6 100644 --- a/subduction/2D/LAMEM_setup2D_sticky.jl +++ b/subduction/2D/LAMEM_setup2D_sticky.jl @@ -1,10 +1,11 @@ using GeophysicalModelGenerator function GMG_subduction_2D(nx, ny) + model_depth = 660 # Our starting basis is the example above with ridge and overriding slab nx, nz = nx, ny x = range(-2000, 2000, nx); - z = range(-660, 20, nz); + z = range(-model_depth, 20, nz); Grid2D = CartData(xyz_grid(x,0,z)) Phases = zeros(Int64, nx, 1, nz); Temp = fill(1280.0, nx, 1, nz); @@ -17,7 +18,7 @@ function GMG_subduction_2D(nx, ny) Temp, Grid2D; xlim =(-2000, 0), - zlim =(-660.0, 0.0), + zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, phase = lith, T = SpreadingRateTemp( @@ -36,7 +37,7 @@ function GMG_subduction_2D(nx, ny) Temp, Grid2D; xlim =(1500, 2000), - zlim =(-660.0, 0.0), + zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, phase = lith, T = SpreadingRateTemp( @@ -55,7 +56,7 @@ function GMG_subduction_2D(nx, ny) Temp, Grid2D; xlim =(0, 400), - zlim =(-660.0, 0.0), + zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, phase = LithosphericPhases(Layers=[25 90], Phases=[3 4 0] ), T = HalfspaceCoolingTemp( @@ -71,7 +72,7 @@ function GMG_subduction_2D(nx, ny) Temp, Grid2D; xlim =(400, 1500), - zlim =(-660.0, 0.0), + zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, phase = LithosphericPhases(Layers=[35 100], Phases=[3 4 0] ), T = HalfspaceCoolingTemp( @@ -86,7 +87,7 @@ function GMG_subduction_2D(nx, ny) Temp, Grid2D; xlim =(0, 300), - zlim =(-660.0, 0.0), + zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=30, phase = LithosphericPhases(Layers=[30 80], Phases=[1 2 0], Tlab=1250 ), T = HalfspaceCoolingTemp( @@ -116,3 +117,21 @@ function GMG_subduction_2D(nx, ny) return li, origin, ph, Temp[:,1,:].+273 end + + +function struct2cpu(a::T) where T<:Union{StokesArrays, SymmetricTensor, ThermalArrays, Velocity, Residual} + nfields = fieldcount(T) + cpu_fields = ntuple(Val(nfields)) do i + struct2cpu(getfield(a, i)) + end + return T(cpu_fields...) +end + +# @inline struct2cpu(A::Array) = A +# @inline struct2cpu(A::AbstractArray) = Array(A) + +# struct2cpu(stokes) + +# foo(::T) where T = T(rand(2,2),rand(2,2)) +# foo(T.name.Typeofwrapper) +# T.name.Typeofwrapper[1] \ No newline at end of file From 49f90b3dce3f080b8a5b7c630426e986871832dd Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 8 May 2024 11:24:34 +0200 Subject: [PATCH 39/60] updates --- src/boundaryconditions/BoundaryConditions.jl | 22 ++- src/common.jl | 2 +- src/ext/AMDGPU/2D.jl | 42 ++++- src/ext/AMDGPU/3D.jl | 40 ++++- src/ext/CUDA/2D.jl | 42 ++++- src/ext/CUDA/3D.jl | 42 ++++- src/particles/subgrid_diffusion.jl | 86 +---------- src/phases/phases.jl | 74 +++------ src/rheology/Viscosity.jl | 20 --- src/stokes/Stokes2D.jl | 153 +------------------ subduction/2D/LAMEM2D_sticky.jl | 141 ++++++++--------- subduction/2D/LAMEM_setup2D_sticky.jl | 20 +-- 12 files changed, 272 insertions(+), 412 deletions(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index 7bc1ea36..e85489c1 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -1,3 +1,9 @@ +# BOUNDARY CONDITIONS KERNELS +include("free_slip.jl") +include("free_surface.jl") +include("no_slip.jl") +include("pure_shear.jl") + @inline bc_index(x::NTuple{2,T}) where {T} = mapreduce(xi -> max(size(xi)...), max, x) @inline bc_index(x::T) where {T<:AbstractArray{<:Any,2}} = max(size(x)...) @@ -5,11 +11,6 @@ n = mapreduce(xi -> max(size(xi)...), max, x) return n, n end -@inline bc_index(x::T) where {T<:AbstractArray{<:Any,2}} = max(size(x)...) -@inline function bc_index(x::T) where {T<:AbstractArray{<:Any,3}} - nx, ny, nz = size(x) - return max((nx, ny), (ny, nz), (nx, nz)) -end @inline do_bc(bc) = reduce(|, values(bc)) @@ -20,7 +21,7 @@ Apply the prescribed heat boundary conditions `bc` on the `T` """ thermal_bcs!(thermal, bcs) = thermal_bcs!(backend(thermal), thermal, bcs) function thermal_bcs!( - ::CPUBackendTrait, thermal::JustRelax.ThermalArrays, bcs::FlowBoundaryConditions + ::CPUBackendTrait, thermal::JustRelax.ThermalArrays, bcs::TemperatureBoundaryConditions ) return thermal_bcs!(thermal.T, bcs) end @@ -34,8 +35,7 @@ function thermal_bcs!(T::AbstractArray, bcs::TemperatureBoundaryConditions) return nothing end -@inline thermal_bcs!(thermal::ThermalArrays, bcs::TemperatureBoundaryConditions) = - thermal_bcs!(thermal.T, bcs) +# @inline thermal_bcs!(thermal::ThermalArrays, bcs::TemperatureBoundaryConditions) = thermal_bcs!(thermal.T, bcs) """ flow_bcs!(stokes, bcs::FlowBoundaryConditions, di) @@ -60,8 +60,4 @@ function _flow_bcs!(bcs, V) return nothing end -# BOUNDARY CONDITIONS KERNELS -include("free_slip.jl") -include("free_surface.jl") -include("no_slip.jl") -include("pure_shear.jl") + diff --git a/src/common.jl b/src/common.jl index 8c4d3d80..359cfe8f 100644 --- a/src/common.jl +++ b/src/common.jl @@ -35,7 +35,7 @@ export FlowBoundaryConditions, include("MiniKernels.jl") include("phases/phases.jl") -export fn_ratio, phase_ratios_center +export phase_ratios_center!, fn_ratio include("rheology/BuoyancyForces.jl") export compute_ρg! diff --git a/src/ext/AMDGPU/2D.jl b/src/ext/AMDGPU/2D.jl index dcab6325..92743b0f 100644 --- a/src/ext/AMDGPU/2D.jl +++ b/src/ext/AMDGPU/2D.jl @@ -92,14 +92,14 @@ function thermal_bcs!(::AMDGPUBackendTrait, thermal::JustRelax.ThermalArrays, bc end # Phases -function JR2D.phase_ratios_center( +function JR2D.phase_ratios_center!( ::AMDGPUBackendTrait, phase_ratios::JustRelax.PhaseRatio, particles, grid::Geometry, phases, ) - return _phase_ratios_center(phase_ratios, particles, grid, phases) + return _phase_ratios_center!(phase_ratios, particles, grid, phases) end # Rheology @@ -183,4 +183,42 @@ function JR2D.compute_dt(::AMDGPUBackendTrait, S::JustRelax.StokesArrays, args.. return _compute_dt(S, args...) end +# Subgrid diffusion + +function JR2D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::RocArray, + phases::JustRelax.PhaseRatio, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases.center, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + +function JR2D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::RocArray, + phases::AbstractArray{Int,N}, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) where {N} + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + end diff --git a/src/ext/AMDGPU/3D.jl b/src/ext/AMDGPU/3D.jl index 472905e1..acf39f5a 100644 --- a/src/ext/AMDGPU/3D.jl +++ b/src/ext/AMDGPU/3D.jl @@ -92,14 +92,14 @@ function thermal_bcs!(::CUDABackendTrait, thermal::JustRelax.ThermalArrays, bcs) end # Phases -function JR3D.phase_ratios_center( +function JR3D.phase_ratios_center!( ::CUDABackendTrait, phase_ratios::JustRelax.PhaseRatio, particles, grid::Geometry, phases, ) - return _phase_ratios_center(phase_ratios, particles, grid, phases) + return _phase_ratios_center!(phase_ratios, particles, grid, phases) end # Rheology @@ -183,4 +183,40 @@ function JR3D.compute_dt(::CUDABackendTrait, S::JustRelax.StokesArrays, args...) return _compute_dt(S, args...) end +function JR3D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::RocArray, + phases::JustRelax.PhaseRatio, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases.center, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + +function JR3D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::RocArray, + phases::AbstractArray{Int,N}, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) where {N} + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + end diff --git a/src/ext/CUDA/2D.jl b/src/ext/CUDA/2D.jl index e4701169..74deef2d 100644 --- a/src/ext/CUDA/2D.jl +++ b/src/ext/CUDA/2D.jl @@ -92,14 +92,14 @@ function thermal_bcs!(::CUDABackendTrait, thermal::JustRelax.ThermalArrays, bcs) end # Phases -function JR2D.phase_ratios_center( +function JR2D.phase_ratios_center!( ::CUDABackendTrait, phase_ratios::JustRelax.PhaseRatio, particles, grid::Geometry, phases, ) - return _phase_ratios_center(phase_ratios, particles, grid, phases) + return _phase_ratios_center!(phase_ratios, particles, grid, phases) end # Rheology @@ -182,4 +182,42 @@ function JR2D.compute_dt(::CUDABackendTrait, S::JustRelax.StokesArrays, args...) return _compute_dt(S, args...) end +# Subgrid diffusion + +function JR2D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::CuArray, + phases::JustRelax.PhaseRatio, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases.center, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + +function JR2D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::CuArray, + phases::AbstractArray{Int,N}, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) where {N} + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + end diff --git a/src/ext/CUDA/3D.jl b/src/ext/CUDA/3D.jl index 73158c3a..1c33d926 100644 --- a/src/ext/CUDA/3D.jl +++ b/src/ext/CUDA/3D.jl @@ -92,14 +92,14 @@ function thermal_bcs!(::CUDABackendTrait, thermal::JustRelax.ThermalArrays, bcs) end # Phases -function JR3D.phase_ratios_center( +function JR3D.phase_ratios_center!( ::CUDABackendTrait, phase_ratios::JustRelax.PhaseRatio, particles, grid::Geometry, phases, ) - return _phase_ratios_center(phase_ratios, particles, grid, phases) + return _phase_ratios_center!(phase_ratios, particles, grid, phases) end # Rheology @@ -182,4 +182,42 @@ function JR3D.compute_dt(::CUDABackendTrait, S::JustRelax.StokesArrays, args...) return _compute_dt(S, args...) end +# Subgrid diffusion + +function JR3D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::CuArray, + phases::JustRelax.PhaseRatio, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases.center, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + +function JR3D.subgrid_characteristic_time!( + subgrid_arrays, + particles, + dt₀::CuArray, + phases::AbstractArray{Int,N}, + rheology, + thermal::JustRelax.ThermalArrays, + stokes::JustRelax.StokesArrays, + xci, + di, +) where {N} + ni = size(stokes.P) + @parallel (@idx ni) subgrid_characteristic_time!( + dt₀, phases, rheology, thermal.Tc, stokes.P, di + ) + return nothing +end + end diff --git a/src/particles/subgrid_diffusion.jl b/src/particles/subgrid_diffusion.jl index bc457642..c4dbcdd2 100644 --- a/src/particles/subgrid_diffusion.jl +++ b/src/particles/subgrid_diffusion.jl @@ -1,9 +1,7 @@ -# import JustRelax.compute_ρCp - function subgrid_characteristic_time!( subgrid_arrays, particles, - dt₀, + dt₀::Array, phases::JustRelax.PhaseRatio, rheology, thermal::JustRelax.ThermalArrays, @@ -21,8 +19,8 @@ end function subgrid_characteristic_time!( subgrid_arrays, particles, - dt₀, - phases::AbstractArray{Int,N}, + dt₀::Array, + phases::Array{Int,N}, rheology, thermal::JustRelax.ThermalArrays, stokes::JustRelax.StokesArrays, @@ -51,81 +49,3 @@ end return nothing end - -# struct SubgridDiffusionArrays{T, CA} -# pT0::CA # CellArray -# pΔT::CA # CellArray -# ΔT::T # Array - -# function SubgridDiffusionArrays(particles, ni) -# pΔT, pT0 = init_cell_arrays(particles, Val(2)) -# ΔT = @zeros(ni.+1) -# CA, T = typeof(pΔT), typeof(ΔT) -# new{T, CA}(pT0, pΔT, ΔT) -# end -# end - -# @inline function init_cell_arrays(particles, ::Val{N}) where {N} -# return ntuple( -# _ -> @fill( -# 0.0, size(particles.coords[1])..., celldims = (cellsize(particles.index)) -# ), -# Val(N), -# ) -# end - -# function subgrid_diffusion!( -# pT, thermal, subgrid_arrays, pPhases, rheology, stokes, particles, T_buffer, xvi, di, dt -# ) -# (; pT0, pΔT) = subgrid_arrays -# # ni = size(pT) - -# @copy pT0.data pT.data -# grid2particle!(pT, xvi, T_buffer, particles) - -# @parallel (@idx ni) subgrid_diffusion!(pT, pT0, pΔT, pPhases, rheology, stokes.P, particles.index, di, dt) -# particle2grid!(subgrid_arrays.ΔT, pΔT, xvi, particles) - -# @parallel (@idx size(subgrid_arrays.ΔT)) update_ΔT_subgrid!(subgrid_arrays.ΔT, thermal.ΔT) -# grid2particle!(pΔT, xvi, subgrid_arrays.ΔT, particles) -# @. pT.data = pT0.data + pΔT.data -# return nothing -# end - -# @parallel_indices (i, j) function update_ΔT_subgrid!(ΔTsubgrid::_T, ΔT::_T) where _T<:AbstractMatrix -# ΔTsubgrid[i, j] = ΔT[i+1, j] - ΔTsubgrid[i, j] -# return nothing -# end - -# function subgrid_diffusion!(pT, pT0, pΔT, pPhases, rheology, stokes, particles, di, dt) -# ni = size(pT) -# @parallel (@idx ni) subgrid_diffusion!(pT, pT0, pΔT, pPhases, rheology, stokes.P, particles.index, di, dt) -# end - -# @parallel_indices (I...) function subgrid_diffusion!(pT, pT0, pΔT, pPhases, rheology, P, index, di, dt) - -# P_cell = P[I...] - -# for ip in JustRelax.cellaxes(pT) -# # early escape if there is no particle in this memory locations -# doskip(index, ip, I...) && continue - -# pT0ᵢ = @cell pT0[ip, I...] -# pTᵢ = @cell pT[ip, I...] -# phase = Int(@cell(pPhases[ip, I...])) -# argsᵢ = (; T = pTᵢ, P = P_cell) -# # dimensionless numerical diffusion coefficient (0 ≤ d ≤ 1) -# d = 1 -# # Compute the characteristic timescale `dt₀` of the local cell -# ρCp = compute_ρCp(rheology, phase, argsᵢ) -# K = compute_conductivity(rheology, phase, argsᵢ) -# sum_dxi = mapreduce(x-> inv(x)^2, +, di) -# dt₀ = ρCp / (2 * K * sum_dxi) -# # subgrid diffusion of the i-th particle -# pΔTᵢ = (pTᵢ - pT0ᵢ) * (1-exp(-d * dt / dt₀)) -# @cell pT0[ip, I...] = pT0ᵢ + pΔTᵢ -# @cell pΔT[ip, I...] = pΔTᵢ -# end - -# return nothing -# end diff --git a/src/phases/phases.jl b/src/phases/phases.jl index 22452832..710f789d 100644 --- a/src/phases/phases.jl +++ b/src/phases/phases.jl @@ -48,27 +48,27 @@ end end end -function phase_ratios_center(phase_ratios, particles, grid, phases) - return phase_ratios_center(backend(phase_ratios), phase_ratios, particles, grid, phases) +function phase_ratios_center!(phase_ratios, particles, grid, phases) + return phase_ratios_center!(backend(phase_ratios), phase_ratios, particles, grid, phases) end -function phase_ratios_center( +function phase_ratios_center!( ::CPUBackendTrait, phase_ratios::JustRelax.PhaseRatio, particles, grid::Geometry, phases ) - return _phase_ratios_center(phase_ratios, particles, grid, phases) + return _phase_ratios_center!(phase_ratios, particles, grid, phases) end -function _phase_ratios_center( +function _phase_ratios_center!( phase_ratios::JustRelax.PhaseRatio, particles, grid::Geometry, phases ) ni = size(phases) - @parallel (@idx ni) phase_ratios_center_kernel( + @parallel (@idx ni) phase_ratios_center_kernel!( phase_ratios.center, particles.coords, grid.xci, grid.di, phases ) return nothing end -@parallel_indices (I...) function phase_ratios_center_kernel( +@parallel_indices (I...) function phase_ratios_center_kernel!( ratio_centers, pxi::NTuple{N,T1}, xci::NTuple{N,T2}, di::NTuple{N,T3}, phases ) where {N,T1,T2,T3} @@ -89,50 +89,24 @@ end function phase_ratio_weights( pxi::NTuple{NP,C}, ph::SVector{N1,T}, cell_center, di, ::Val{NC} ) where {N1,NC,NP,T,C} - if @generated - quote - Base.@_inline_meta - # Initiaze phase ratio weights (note: can't use ntuple() here because of the @generated function) - Base.@nexprs $NC i -> w_i = zero($T) - w = Base.@ncall $NC tuple w - - # initialie sum of weights - sumw = zero($T) - Base.@nexprs $N1 i -> begin - # bilinear weight (1-(xᵢ-xc)/dx)*(1-(yᵢ-yc)/dy) - x = bilinear_weight(cell_center, getindex.(pxi, i), di) - sumw += x # reduce - ph_local = ph[i] - # this is doing sum(w * δij(i, phase)), where δij is the Kronecker delta - # Base.@nexprs $NC j -> tmp_j = w[j] + x * δ(Int(ph_local), j) - Base.@nexprs $NC j -> tmp_j = w[j] + x * (ph_local == j) - w = Base.@ncall $NC tuple tmp - end - - # return phase ratios weights w = sum(w * δij(i, phase)) / sum(w) - _sumw = inv(sum(w)) - Base.@nexprs $NC i -> w_i = w[i] * _sumw - w = Base.@ncall $NC tuple w - return w - end - else - # Initiaze phase ratio weights (note: can't use ntuple() here because of the @generated function) - w = ntuple(_ -> zero(T), Val(NC)) - # initialie sum of weights - sumw = zero(T) - - for i in eachindex(pxi) - # bilinear weight (1-(xᵢ-xc)/dx)*(1-(yᵢ-yc)/dy) - x = @inline bilinear_weight(cell_center, getindex.(pxi, i), di) - sumw += x # reduce - ph_local = ph[i] - # this is doing sum(w * δij(i, phase)), where δij is the Kronecker delta - # w = w .+ x .* ntuple(j -> δ(Int(ph_local), j), Val(NC)) - w = w .+ x .* ntuple(j -> (ph_local == j), Val(NC)) - end - w = w .* inv(sum(w)) - return w + + # Initiaze phase ratio weights (note: can't use ntuple() here because of the @generated function) + w = ntuple(_ -> zero(T), Val(NC)) + sumw = zero(T) + + for i in eachindex(ph) + # bilinear weight (1-(xᵢ-xc)/dx)*(1-(yᵢ-yc)/dy) + p = getindex.(pxi, i) + isnan(first(p)) && continue + x = @inline bilinear_weight(cell_center, p, di) + sumw += x # reduce + ph_local = ph[i] + # this is doing sum(w * δij(i, phase)), where δij is the Kronecker delta + # w = w .+ x .* ntuple(j -> δ(Int(ph_local), j), Val(NC)) + w = w .+ x .* ntuple(j -> (ph_local == j), Val(NC)) end + w = w .* inv(sumw) + return w end @generated function bilinear_weight( diff --git a/src/rheology/Viscosity.jl b/src/rheology/Viscosity.jl index 126d625f..dad98a82 100644 --- a/src/rheology/Viscosity.jl +++ b/src/rheology/Viscosity.jl @@ -1,23 +1,3 @@ -function compute_viscosity!(η, ν, stokes::StokesArrays, args, rheology, cutoff) - ni = size(η) - @parallel (@idx ni) compute_viscosity!(η, ν, @strain(stokes)..., args, rheology, cutoff) -end - -function compute_viscosity!(η, ν, εII::AbstractArray, args, rheology, cutoff) - ni = size(η) - @parallel (@idx ni) compute_viscosity!(η, ν, εII, args, rheology, cutoff) -end - -function compute_viscosity!( - η, ν, phase_ratios::PhaseRatio, stokes::StokesArrays, args, rheology, cutoff -) - ni = size(η) - @parallel (@idx ni) compute_viscosity!( - η, ν, phase_ratios.center, @strain(stokes)..., args, rheology, cutoff - ) -end - -## 2D KERNELS function compute_viscosity!( stokes::JustRelax.StokesArrays, args, rheology, cutoff; relaxation=1e0 ) diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 0048cc8a..f2f14239 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -595,7 +595,8 @@ function _solve!( dt * free_surface, ) # apply boundary conditions - free_surface_bcs!(stokes, flow_bcs, args, η, rheology, phase_ratios, dt, di) + # free_surface_bcs!(stokes, flow_bcs, args, η, rheology, phase_ratios, dt, di) + free_surface_bcs!(stokes, flow_bcs, η, rheology, phase_ratios, dt, di) # free_surface_bcs!(stokes, flow_bcs, η, rheology, phase_ratios, dt, di) flow_bcs!(stokes, flow_bcs) update_halo!(@velocity(stokes)...) @@ -650,14 +651,14 @@ function _solve!( end end - stokes.P .= θ + # stokes.P .= θ @parallel (@idx ni .+ 1) multi_copy!(@tensor(stokes.τ_o), @tensor(stokes.τ)) @parallel (@idx ni) multi_copy!(@tensor_center(stokes.τ_o), @tensor_center(stokes.τ)) # accumulate plastic strain tensor @parallel (@idx ni) accumulate_tensor!(stokes.EII_pl, @tensor_center(stokes.ε_pl), dt) - compute_vorticity!(stokes, di) + # compute_vorticity!(stokes, di) # @parallel (@idx ni .+ 1) multi_copy!(@tensor(stokes.τ_o), @tensor(stokes.τ)) # @parallel (@idx ni) multi_copy!( @@ -674,149 +675,3 @@ function _solve!( ) end - -function _solve!( - stokes::JustRelax.StokesArrays, - thermal::JustRelax.ThermalArrays, - pt_stokes, - di::NTuple{2,T}, - flow_bcs, - ϕ, - ρg, - phase_v, - phase_c, - args_η, - rheology::NTuple{N,MaterialParams}, - dt, - igg::IGG; - iterMax=10e3, - nout=500, - b_width=(4, 4, 1), - verbose=true, - kwargs..., -) where {N,T} - - # unpack - - _di = inv.(di) - (; ϵ, r, θ_dτ, ηdτ) = pt_stokes - (; η, η_vep) = stokes.viscosity - ni = size(stokes.P) - # ~preconditioner - ητ = deepcopy(η) - # @hide_communication b_width begin # communication/computation overlap - compute_maxloc!(ητ, η; window=(1, 1)) - update_halo!(ητ) - # end - - # errors - err = 2 * ϵ - iter = 0 - err_evo1 = Float64[] - err_evo2 = Float64[] - norm_Rx = Float64[] - norm_Ry = Float64[] - norm_∇V = Float64[] - - # solver loop - wtime0 = 0.0 - while iter < 2 || (err > ϵ && iter ≤ iterMax) - wtime0 += @elapsed begin - @parallel (@idx ni) compute_∇V!(stokes.∇V, @velocity(stokes)..., _di...) - @parallel (@idx ni) compute_P!( - stokes.P, - stokes.P0, - stokes.R.RP, - stokes.∇V, - η, - rheology, - phase_c, - dt, - r, - θ_dτ, - ) - @parallel (@idx ni .+ 1) compute_strain_rate!( - @strain(stokes)..., stokes.∇V, @velocity(stokes)..., _di... - ) - @parallel (@idx ni) compute_ρg!( - ρg[end], ϕ, rheology, (T=thermal.Tc, P=stokes.P) - ) - @parallel (@idx ni) compute_τ_gp!( - @tensor_center(stokes.τ), - stokes.τ.II, - @tensor(stokes.τ_o), - @strain(stokes), - η, - η_vep, - thermal.T, - phase_v, - phase_c, - args_η, - rheology, # needs to be a tuple - dt, - θ_dτ, - ) - @parallel center2vertex!(stokes.τ.xy, stokes.τ.xy_c) - @hide_communication b_width begin # communication/computation overlap - @parallel compute_V!( - @velocity(stokes)..., - stokes.P, - @stress(stokes)..., - pt_stokes.ηdτ, - ρg..., - ητ, - _di..., - dt, - ) - # apply boundary conditions boundary conditions - flow_bcs!(stokes, flow_bcs) - update_halo!(stokes.V.Vx, stokes.V.Vy) - end - end - - iter += 1 - if iter % nout == 0 && iter > 1 - @parallel (@idx ni) compute_Res!( - stokes.R.Rx, - stokes.R.Ry, - @velocity(stokes)..., - stokes.P, - @stress(stokes)..., - ρg..., - _di..., - dt, - ) - errs = maximum_mpi.((abs.(stokes.R.Rx), abs.(stokes.R.Ry), abs.(stokes.R.RP))) - push!(norm_Rx, errs[1]) - push!(norm_Ry, errs[2]) - push!(norm_∇V, errs[3]) - err = maximum_mpi(errs) - push!(err_evo1, err) - push!(err_evo2, iter) - if igg.me == 0 && (verbose || iter == iterMax) - @printf( - "Total steps = %d, err = %1.3e [norm_Rx=%1.3e, norm_Ry=%1.3e, norm_∇V=%1.3e] \n", - iter, - err, - norm_Rx[end], - norm_Ry[end], - norm_∇V[end] - ) - end - isnan(err) && error("NaN(s)") - end - - if igg.me == 0 && err ≤ ϵ - println("Pseudo-transient iterations converged in $iter iterations") - end - end - - return ( - iter=iter, - err_evo1=err_evo1, - err_evo2=err_evo2, - norm_Rx=norm_Rx, - norm_Ry=norm_Ry, - norm_∇V=norm_∇V, - ) -end diff --git a/subduction/2D/LAMEM2D_sticky.jl b/subduction/2D/LAMEM2D_sticky.jl index cb80d741..479f3bba 100644 --- a/subduction/2D/LAMEM2D_sticky.jl +++ b/subduction/2D/LAMEM2D_sticky.jl @@ -1,41 +1,41 @@ -const isCUDA = true +# const isCUDA = true +const isCUDA = false + +using TimerOutputs @static if isCUDA using CUDA end -using JustRelax, JustRelax.DataIO +using JustRelax, JustRelax.JustRelax2D, JustRelax.DataIO import JustRelax.@cell -using ParallelStencil + +const backend = @static if isCUDA + CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +else + JustRelax.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +end + +using ParallelStencil, ParallelStencil.FiniteDifferences2D + @static if isCUDA @init_parallel_stencil(CUDA, Float64, 2) else @init_parallel_stencil(Threads, Float64, 2) end -using JustPIC -using JustPIC._2D +using JustPIC, JustPIC._2D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -const backend = @static if isCUDA +const backend_JP = @static if isCUDA CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend else - CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend -end - -# setup ParallelStencil.jl environment - -@static if isCUDA - model = PS_Setup(:CUDA, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) - environment!(model) -else - model = PS_Setup(:cpu, Float64, 2) # or (:CUDA, Float64, 3) or (:AMDGPU, Float64, 3) - environment!(model) + JustPIC.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend end # Load script dependencies -using Printf, LinearAlgebra, GeoParams, GLMakie, CellArrays +using GeoParams, GLMakie, CellArrays # Load file with all the rheology configurations include("LAMEM_rheology.jl") @@ -88,7 +88,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk max_xcell = 60 min_xcell = 20 particles = init_particles( - backend, nxcell, max_xcell, min_xcell, xvi, di, ni + backend_JP, nxcell, max_xcell, min_xcell, xvi, di, ni ) subgrid_arrays = SubgridDiffusionCellArrays(particles) # velocity grids @@ -100,30 +100,30 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk particle_args = (pT, τxx_p, τyy_p, τxy_p, vorticity_p, pPhases) # Assign particles phases anomaly - phases_device = PTArray(phases_GMG) + phases_device = PTArray(backend)(phases_GMG) init_phases!(pPhases, phases_device, particles, xvi) - phase_ratios = PhaseRatio(ni, length(rheology)) - phase_ratios_center!(phase_ratios, particles, xci, di, pPhases) + phase_ratios = PhaseRatio(backend, ni, length(rheology)) + phase_ratios_center!(phase_ratios, particles, grid, pPhases) # ---------------------------------------------------- # STOKES --------------------------------------------- # Allocate arrays needed for every Stokes problem - stokes = StokesArrays(ni, ViscoElastic) + stokes = StokesArrays(backend, ni) pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, Re=3π, r=1e0, CFL = 1 / √2.1) # Re=3π, r=0.7 # ---------------------------------------------------- # TEMPERATURE PROFILE -------------------------------- Ttop = 20 + 273 Tbot = 1565.0 + 273 - thermal = ThermalArrays(ni) - @views thermal.T[2:end-1, :] .= PTArray(T_GMG) + thermal = ThermalArrays(backend, ni) + @views thermal.T[2:end-1, :] .= PTArray(backend)(T_GMG) thermal_bc = TemperatureBoundaryConditions(; no_flux = (left = true, right = true, top = false, bot = false), ) thermal_bcs!(thermal, thermal_bc) @views thermal.T[:, end] .= Ttop @views thermal.T[:, 1] .= Tbot - @parallel (@idx ni) temperature2center!(thermal.Tc, thermal.T) + temperature2center!(thermal) # ---------------------------------------------------- # Buoyancy forces @@ -138,16 +138,12 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) # Rheology - η = @ones(ni...) - η_vep = similar(η) - args0 = (; T = thermal.Tc, P = Plitho, dt = Inf) - compute_viscosity!( - η, 1.0, phase_ratios, stokes, args0, rheology, (1e18, 1e24) - ) + args0 = (T=thermal.Tc, P=Plitho, dt = Inf) + compute_viscosity!(stokes, phase_ratios, args0, rheology, (1e18, 1e24)) # PT coefficients for thermal diffusion pt_thermal = PTThermalCoeffs( - rheology_augmented, phase_ratios, args0, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 + backend, rheology_augmented, phase_ratios, args0, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 ) # Boundary conditions @@ -205,7 +201,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Vertice arrays of normal components of the stress tensor τxx_vertex, τyy_vertex = @zeros(ni.+1...), @zeros(ni.+1...) τxx_o_vertex, τyy_o_vertex = @zeros(ni.+1...), @zeros(ni.+1...) - + while it < 1000 # run only for 5 Myrs # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs @@ -217,15 +213,13 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk thermal_bcs!(thermal, thermal_bc) temperature2center!(thermal) - # # Update buoyancy and viscosity - + # Update buoyancy and viscosity - Plitho .= reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) args = (; T = thermal.Tc, P = Plitho, dt=Inf) # args = (; T = thermal.Tc, P = stokes.P, dt=Inf) - compute_viscosity!( - η, 1.0, phase_ratios, stokes, args, rheology, (1e18, 1e24) - ) + compute_viscosity!(stokes, phase_ratios, args, rheology, (1e18, 1e24)) compute_ρg!(ρg[2], phase_ratios, rheology, args) # Stokes solver ---------------- @@ -236,25 +230,25 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk di, flow_bcs, ρg, - η, - η_vep, phase_ratios, rheology, args, dt, igg; - iterMax = 10e3, - nout = 1e3, - viscosity_cutoff = (1e18, 1e24), - free_surface = false, - viscosity_relaxation = 1e-2 + kwargs = ( + iterMax = 50e3, + nout = 1e3, + viscosity_cutoff = (1e18, 1e24), + free_surface = false, + viscosity_relaxation = 1e-2 + ) ); end println("Stokes solver time ") println(" Total time: $t_stokes s") println(" Time/iteration: $(t_stokes / out.iter) s") - @parallel (JustRelax.@idx ni) JustRelax.Stokes2D.tensor_invariant!(stokes.ε.II, @strain(stokes)...) - dt = compute_dt(stokes, di) + tensor_invariant!(stokes.ε) + dt = compute_dt(stokes, di) * 0.8 # ------------------------------ # Thermal solver --------------- @@ -266,26 +260,29 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk args, dt, di; - igg = igg, - phase = phase_ratios, - iterMax = 150e3, - nout = 2e3, - verbose = true, + kwargs = ( + igg = igg, + phase = phase_ratios, + iterMax = 150e3, + nout = 2e3, + verbose = true, + ) ) - subgrid_characteristic_time!( + to = TimerOutput() + @timeit to "subgrid time" subgrid_characteristic_time!( subgrid_arrays, particles, dt₀, phase_ratios, rheology_augmented, thermal, stokes, xci, di ) - centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) - subgrid_diffusion!( + @timeit to "centroid2par" centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + @timeit to "subgrid diff" subgrid_diffusion!( pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt ) # ------------------------------ # Advection -------------------- # advect particles in space - advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) + @timeit to "advect" advection!(particles, RungeKutta2(2/3), @velocity(stokes), grid_vxi, dt) # advect particles in memory - move_particles!(particles, xvi, particle_args) + @timeit to "move" move_particles!(particles, xvi, particle_args) # JustRelax.Stokes2D.rotate_stress_particles!( # stokes, # τxx_vertex, @@ -302,19 +299,21 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # ) # check if we need to inject particles - inject_particles_phase!(particles, pPhases, (pT, τxx_p, τyy_p, τxy_p, vorticity_p), (T_buffer, τxx_vertex, τyy_vertex, stokes.τ.xy, stokes.ω.xy_v), xvi) + @timeit to "inject" inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer, ), xvi) + # inject_particles_phase!(particles, pPhases, (pT, τxx_p, τyy_p, τxy_p, vorticity_p), (T_buffer, τxx_vertex, τyy_vertex, stokes.τ.xy, stokes.ω.xy_v), xvi) # update phase ratios - @parallel (@idx ni) phase_ratios_center(phase_ratios.center, particles.coords, xci, di, pPhases) + @timeit to "phase ratios" phase_ratios_center!(phase_ratios, particles, grid, pPhases) + @show to @show it += 1 t += dt # Data I/O and plotting --------------------- if it == 1 || rem(it, 5) == 0 - checkpointing(figdir, stokes, thermal.T, η, t) - + # checkpointing(figdir, stokes, thermal.T, η, t) + (; η_vep, η) = stokes.viscosity if do_vtk - JustRelax.velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) + velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) data_v = (; T = Array(T_buffer), τxy = Array(stokes.τ.xy), @@ -394,7 +393,7 @@ figdir = "Subduction_LAMEM_2D" n = 128 nx, ny = n*6, n nx, ny = 512, 128 -# nx, ny = 512, 132 +nx, ny = 32*6, 32 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) @@ -402,19 +401,23 @@ else igg end -main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); +# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); + + +# Xv = [x for x in xvi[1], y in xvi[2]][:]; +# Yv = [y for x in xvi[1], y in xvi[2]][:]; +# p = phases_GMG[:]; -# c = Array(phase_ratios.center) -# a = [any(isnan, x) for x in c] +# scatter!(Xv./1e3, Yv./1e3, color = p, markersize = 10, colormap=:inferno) -# any(isnan, c[1]) +# init_phases!(pPhases, phases_device, particles, xvi) +# # Make particles plottable # p = particles.coords # ppx, ppy = p # pxv = ppx.data[:]./1e3 # pyv = ppy.data[:]./1e3 # clr = pPhases.data[:] -# # clr = pT.data[:] # idxv = particles.index.data[:]; -# scatter!(Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv]), markersize = 3) +# scatter!(Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) \ No newline at end of file diff --git a/subduction/2D/LAMEM_setup2D_sticky.jl b/subduction/2D/LAMEM_setup2D_sticky.jl index 1d44f9b6..b85bed44 100644 --- a/subduction/2D/LAMEM_setup2D_sticky.jl +++ b/subduction/2D/LAMEM_setup2D_sticky.jl @@ -116,22 +116,4 @@ function GMG_subduction_2D(nx, ny) ph = Phases[:,1,:] .+ 1 return li, origin, ph, Temp[:,1,:].+273 -end - - -function struct2cpu(a::T) where T<:Union{StokesArrays, SymmetricTensor, ThermalArrays, Velocity, Residual} - nfields = fieldcount(T) - cpu_fields = ntuple(Val(nfields)) do i - struct2cpu(getfield(a, i)) - end - return T(cpu_fields...) -end - -# @inline struct2cpu(A::Array) = A -# @inline struct2cpu(A::AbstractArray) = Array(A) - -# struct2cpu(stokes) - -# foo(::T) where T = T(rand(2,2),rand(2,2)) -# foo(T.name.Typeofwrapper) -# T.name.Typeofwrapper[1] \ No newline at end of file +end \ No newline at end of file From be121eae02238a623ad4dbd0ec1528b5ffdb9a1a Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 8 May 2024 11:25:19 +0200 Subject: [PATCH 40/60] docs --- docs/src/man/equations.md | 52 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/src/man/equations.md b/docs/src/man/equations.md index 0154a877..d11fa32c 100644 --- a/docs/src/man/equations.md +++ b/docs/src/man/equations.md @@ -1,7 +1,55 @@ +# Stokes equations + +Stokes equations for compressible flow are described by + +$$\nabla\cdot\boldsymbol{\tau} + \nabla p = \boldsymbol{f} $$ + +$$\nabla\cdot\boldsymbol{v} + \beta \frac{\partial p}{\partial t} + \alpha \frac{\partial T}{\partial t} = 0$$ + +where $\nabla = \left(\frac{\partial }{\partial x_i}, ..., \frac{\partial }{\partial x_n} \right)$ is the nabla operator, $\boldsymbol{\tau}$ is the deviatoric stress tensor, $p$ is the pressure, $\boldsymbol{f}$ is the body forces vector, $\boldsymbol{v}$ is the velocity field, and $\beta$ is in the inverse of the bulk modulus. In the simple case of a linear rheology, the constitutive equation for an isotropic flow is $\boldsymbol{\tau} = 2\eta\dot{\boldsymbol{\varepsilon}}$, where $\dot{\boldsymbol{\varepsilon}}$ is the deviatoric strain rate tensor. Heat diffusion + +$$\rho C_p \frac{\partial T}{\partial t} = - \nabla q + Q$$ + +$$q = - K \nabla T$$ + +where $\rho$ is the density, $C_p$ is the heat diffusion, $K$ is the heat conductivity, $Q$ is the sum of any amount of source terms, and $T$ is the temperature. + # Pseudo-transient iterative method +The pseudo-transient method consists in augmenting the right-hand-side of the target PDE with a pseudo-time derivative (where $\psi$ is the pseudo-time) of the primary variables. + +## Stokes + +The psuedo-transient formulation of the Stokes equations yields: + +$$\widetilde{\rho}\frac{\partial \boldsymbol{u}}{\partial \psi} + \nabla\cdot\boldsymbol{\tau} - \nabla p = \boldsymbol{f}$$ + +$$\frac{1}{\widetilde{K}}\frac{\partial p}{\partial \psi} + \nabla\cdot\boldsymbol{v} = \beta \frac{\partial p}{\partial t} + \alpha \frac{\partial T}{\partial t}$$ + +We also do a continuation on the constitutive law: + +$$\frac{1}{2\widetilde{G}} \frac{\partial\boldsymbol{\tau}}{\partial\psi}+ \frac{1}{2G}\frac{D\boldsymbol{\tau}}{Dt} + \frac{\boldsymbol{\tau}}{2\eta} = \dot{\boldsymbol{\varepsilon}}$$ + +where the wide tile denotes the effective damping coefficients and $\psi$ is the pseudo-time step. These are defined as in [Raess et al, 2022](https://doi.org/10.5194/gmd-2021-411): + +$$\widetilde{\rho} = Re\frac{\eta}{\widetilde{V}L}, \qquad + \widetilde{G} = \frac{\widetilde{\rho} \widetilde{V}^2}{r+2}, \qquad + \widetilde{K} = r \widetilde{G}$$ + +and + +$$\widetilde{V} = \sqrt{ \frac{\widetilde{K} +2\widetilde{G}}{\widetilde{\rho}}}, \qquad + r = \frac{\widetilde{K}}{\widetilde{G}}, \qquad + Re = \frac{\widetilde{\rho}\widetilde{V}L}{\eta}$$ + +where the P-wave $\widetilde{V}=V_p$ is the characteristic velocity scale for Stokes, and $Re$ is the Reynolds number. + ## Heat diffusion -## Stokes equations +The pseudo-transient heat-diffusion equation is: + +$$\widetilde{\rho}\frac{\partial T}{\partial \psi} + \rho C_p \frac{\partial T}{\partial t} = \nabla(K\nabla T) = -\nabla q$$ + +We use a second order pseudo-transient scheme were continuation is also done on the flux, so that: -## Constitutive equations +$$\widetilde{\theta}\frac{\partial q}{\partial \psi} + q = -K\nabla T$$ From 4e6b0cf03ac317310df3bf5a181ce29612d82917 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 8 May 2024 11:29:58 +0200 Subject: [PATCH 41/60] fix syntax --- src/ext/CUDA/3D.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ext/CUDA/3D.jl b/src/ext/CUDA/3D.jl index 744c8f2b..d6ec2864 100644 --- a/src/ext/CUDA/3D.jl +++ b/src/ext/CUDA/3D.jl @@ -92,7 +92,6 @@ function thermal_bcs!(::CUDABackendTrait, thermal::JustRelax.ThermalArrays, bcs) end # Phases -function JR3D.phase_ratios_center!( function JR3D.phase_ratios_center!( ::CUDABackendTrait, phase_ratios::JustRelax.PhaseRatio, From 0618c0a9c28aab28bad89f7885f06fea954f3952 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 8 May 2024 18:19:30 +0200 Subject: [PATCH 42/60] updates --- src/common.jl | 6 +- src/ext/CUDA/3D.jl | 22 ++++---- src/stokes/Stokes2D.jl | 3 + src/stokes/StressRotation.jl | 9 --- src/stokes/StressRotationParticles.jl | 22 +------- src/stokes/VorticityKernels.jl | 19 +++++++ src/types/constructors/stokes.jl | 23 +++++++- src/types/stokes.jl | 24 ++++++-- subduction/2D/LAMEM2D_sticky.jl | 81 ++++++++++++++------------- subduction/2D/LAMEM_setup2D_sticky.jl | 2 +- 10 files changed, 122 insertions(+), 89 deletions(-) create mode 100644 src/stokes/VorticityKernels.jl diff --git a/src/common.jl b/src/common.jl index 359cfe8f..32216d31 100644 --- a/src/common.jl +++ b/src/common.jl @@ -59,12 +59,16 @@ export WENO5, WENO_advection! include("rheology/GeoParams.jl") include("rheology/StressUpdate.jl") -include("stokes/StressRotation.jl") +include("stokes/StressRotationParticles.jl") +export rotate_stress_particles! + include("stokes/StressKernels.jl") export tensor_invariant! include("stokes/PressureKernels.jl") include("stokes/VelocityKernels.jl") +include("stokes/VorticityKernels.jl") +export compute_vorticity! # thermal diffusion diff --git a/src/ext/CUDA/3D.jl b/src/ext/CUDA/3D.jl index d6ec2864..8e7caab9 100644 --- a/src/ext/CUDA/3D.jl +++ b/src/ext/CUDA/3D.jl @@ -106,31 +106,31 @@ end # Rheology ## viscosity -function JR3D.compute_viscosity!(::AMDGPUBackendTrait, stokes, ν, args, rheology, cutoff) +function JR3D.compute_viscosity!(::CUDABackendTrait, stokes, ν, args, rheology, cutoff) return _compute_viscosity!(stokes, ν, args, rheology, cutoff) end function JR3D.compute_viscosity!( - ::AMDGPUBackendTrait, stokes, ν, phase_ratios, args, rheology, cutoff + ::CUDABackendTrait, stokes, ν, phase_ratios, args, rheology, cutoff ) return _compute_viscosity!(stokes, ν, phase_ratios, args, rheology, cutoff) end -function JR2D.compute_viscosity!(η, ν, εII::RocArray, args, rheology, cutoff) +function JR3D.compute_viscosity!(η, ν, εII::CuArray, args, rheology, cutoff) return compute_viscosity!(η, ν, εII, args, rheology, cutoff) end -function compute_viscosity!(::AMDGPUBackendTrait, stokes, ν, args, rheology, cutoff) +function compute_viscosity!(::CUDABackendTrait, stokes, ν, args, rheology, cutoff) return _compute_viscosity!(stokes, ν, args, rheology, cutoff) end function compute_viscosity!( - ::AMDGPUBackendTrait, stokes, ν, phase_ratios, args, rheology, cutoff + ::CUDABackendTrait, stokes, ν, phase_ratios, args, rheology, cutoff ) return _compute_viscosity!(stokes, ν, phase_ratios, args, rheology, cutoff) end -function compute_viscosity!(η, ν, εII::RocArray, args, rheology, cutoff) +function compute_viscosity!(η, ν, εII::CuArray, args, rheology, cutoff) return compute_viscosity!(η, ν, εII, args, rheology, cutoff) end @@ -140,10 +140,10 @@ function JR3D.tensor_invariant!(::CUDABackendTrait, A::JustRelax.SymmetricTensor end ## Buoyancy forces -function JR3D.compute_ρg!(ρg::RocArray, rheology, args) +function JR3D.compute_ρg!(ρg::CuArray, rheology, args) return compute_ρg!(ρg, rheology, args) end -function JR3D.compute_ρg!(ρg::RocArray, phase_ratios::JustRelax.PhaseRatio, rheology, args) +function JR3D.compute_ρg!(ρg::CuArray, phase_ratios::JustRelax.PhaseRatio, rheology, args) return compute_ρg!(ρg, phase_ratios, rheology, args) end @@ -156,17 +156,17 @@ function temperature2center!(::CUDABackendTrait, thermal::JustRelax.ThermalArray return _temperature2center!(thermal) end -function JR3D.vertex2center!(center::T, vertex::T) where {T<:RocArray} +function JR3D.vertex2center!(center::T, vertex::T) where {T<:CuArray} return vertex2center!(center, vertex) end -function JR3D.center2vertex!(vertex::T, center::T) where {T<:RocArray} +function JR3D.center2vertex!(vertex::T, center::T) where {T<:CuArray} return center2vertex!(vertex, center) end function JR3D.center2vertex!( vertex_yz::T, vertex_xz::T, vertex_xy::T, center_yz::T, center_xz::T, center_xy::T -) where {T<:RocArray} +) where {T<:CuArray} return center2vertex!(vertex_yz, vertex_xz, vertex_xy, center_yz, center_xz, center_xy) end diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index c666a2ad..9ee08f1d 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -509,6 +509,9 @@ function _solve!( Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg) .* di[2], dims=2), dims=2), dims=2) args.P .= stokes.P .+ Plitho .- minimum(stokes.P) + compute_viscosity!(stokes, phase_ratios, args, rheology, viscosity_cutoff) + compute_ρg!(ρg[2], phase_ratios, rheology, args) + while iter ≤ iterMax iterMin < iter && err < ϵ && break diff --git a/src/stokes/StressRotation.jl b/src/stokes/StressRotation.jl index 777e5c03..f64d9250 100644 --- a/src/stokes/StressRotation.jl +++ b/src/stokes/StressRotation.jl @@ -2,15 +2,6 @@ using StaticArrays ## Stress Rotation on the particles -@parallel_indices (i, j) function compute_vorticity!(vorticity, Vx, Vy, _dx, _dy) - dx(A) = _d_xa(A, i, j, _dx) - dy(A) = _d_ya(A, i, j, _dy) - - vorticity[i, j] = 0.5 * (dx(Vy) - dy(Vx)) - - return nothing -end - @parallel_indices (i, j) function rotate_stress_particles_jaumann!(xx, yy, xy, ω, index, dt) cell = i, j diff --git a/src/stokes/StressRotationParticles.jl b/src/stokes/StressRotationParticles.jl index 828026c6..9b2b55e5 100644 --- a/src/stokes/StressRotationParticles.jl +++ b/src/stokes/StressRotationParticles.jl @@ -2,24 +2,6 @@ using StaticArrays # ROTATION KERNELS -## Stress Rotation on the particles -function compute_vorticity!(stokes::StokesArrays, di::NTuple{2}) - ωxy = stokes.ω.xy_c - ni = size(ωxy) - @parallel (@idx ni) compute_vorticity!(ωxy, @velocity(stokes)..., inv.(di)...) - - return nothing -end - -@parallel_indices (i, j) function compute_vorticity!(ωxy, Vx, Vy, _dx, _dy) - dx(A) = _d_xa(A, i, j, _dx) - dy(A) = _d_ya(A, i, j, _dy) - - ωxy[i, j] = 0.5 * (dx(Vy) - dy(Vx)) - - return nothing -end - function jaumann!(xx, yy, xy, ω, index, dt) ni = size(xx) @parallel (@idx ni) _jaumann!(xx, yy, xy, ω, index, dt) @@ -91,7 +73,7 @@ end # STRESS ROTATION ON THE PARTICLES function rotate_stress_particles!( - stokes::StokesArrays, + stokes::JustRelax.StokesArrays, τxx_vertex::T, τyy_vertex::T, τxx_o_vertex::T, @@ -101,7 +83,7 @@ function rotate_stress_particles!( τxy_p::CA, vorticity_p::CA, particles, - grid::Geometry, + grid::JustRelax.Geometry, dt; fn = rotation_matrix! ) where {T<:AbstractArray, CA} diff --git a/src/stokes/VorticityKernels.jl b/src/stokes/VorticityKernels.jl new file mode 100644 index 00000000..9c09407a --- /dev/null +++ b/src/stokes/VorticityKernels.jl @@ -0,0 +1,19 @@ + +compute_vorticity!(stokes, di) = compute_vorticity!(backend(stokes), stokes, di) + +function compute_vorticity!(::CPUBackendTrait, stokes, di) + ω_xy = stokes.ω.xy + ni = size(ω_xy) + @parallel (@idx ni) compute_vorticity!(ω_xy, @velocity(stokes)..., inv.(di)...) + + return nothing +end + +@parallel_indices (i, j) function compute_vorticity!(ω_xy, Vx, Vy, _dx, _dy) + dx(A) = _d_xa(A, i, j, _dx) + dy(A) = _d_ya(A, i, j, _dy) + + ω_xy[i, j] = 0.5 * (dx(Vy) - dy(Vx)) + + return nothing +end \ No newline at end of file diff --git a/src/types/constructors/stokes.jl b/src/types/constructors/stokes.jl index db12d424..47067bd5 100644 --- a/src/types/constructors/stokes.jl +++ b/src/types/constructors/stokes.jl @@ -4,7 +4,7 @@ function Velocity(nx::Integer, ny::Integer) nVx = (nx + 1, ny + 2) nVy = (nx + 2, ny + 1) - Vx, Vy = @zeros(nVx...), @zeros(nVy) + Vx, Vy = @zeros(nVx...), @zeros(nVy...) return JustRelax.Velocity(Vx, Vy, nothing) end @@ -13,10 +13,26 @@ function Velocity(nx::Integer, ny::Integer, nz::Integer) nVy = (nx + 2, ny + 1, nz + 2) nVz = (nx + 2, ny + 2, nz + 1) - Vx, Vy, Vz = @zeros(nVx...), @zeros(nVy), @zeros(nVz) + Vx, Vy, Vz = @zeros(nVx...), @zeros(nVy...), @zeros(nVz...) return JustRelax.Velocity(Vx, Vy, Vz) end +## Vorticity type + +function Vorticity(nx::Integer, ny::Integer) + xy = @zeros(nx, ny) + + return JustRelax.Vorticity(nothing, nothing, xy) +end + +function Vorticity(nx::Integer, ny::Integer, nz::Integer) + yz = @zeros(nx, ny, nz) + xz = @zeros(nx, ny, nz) + xy = @zeros(nx, ny, nz) + + return JustRelax.Vorticity(yz, xz, xy) +end + ## Viscosity type function Viscosity(ni::NTuple{N,Integer}) where {N} @@ -80,6 +96,7 @@ function StokesArrays(ni::NTuple{N,Integer}) where {N} P0 = @zeros(ni...) ∇V = @zeros(ni...) V = Velocity(ni...) + ω = Vorticity(ni...) τ = SymmetricTensor(ni...) τ_o = SymmetricTensor(ni...) ε = SymmetricTensor(ni...) @@ -88,5 +105,5 @@ function StokesArrays(ni::NTuple{N,Integer}) where {N} viscosity = Viscosity(ni) R = Residual(ni...) - return JustRelax.StokesArrays(P, P0, V, ∇V, τ, ε, ε_pl, EII_pl, viscosity, τ_o, R) + return JustRelax.StokesArrays(P, P0, V, ∇V, ω, τ, ε, ε_pl, EII_pl, viscosity, τ_o, R) end diff --git a/src/types/stokes.jl b/src/types/stokes.jl index e7b7cd48..65c162aa 100644 --- a/src/types/stokes.jl +++ b/src/types/stokes.jl @@ -12,10 +12,10 @@ Velocity(Vx::T, Vy::T) where {T} = Velocity(Vx, Vy, nothing) Velocity(ni::NTuple{N,Number}) where {N} = Velocity(ni...) function Velocity(::Number, ::Number) - throw(ArgumentError("Velocity dimensions must be given as integers")) + throw(ArgumentError("Dimensions must be given as integers")) end function Velocity(::Number, ::Number, ::Number) - throw(ArgumentError("Velocity dimensions must be given as integers")) + throw(ArgumentError("Dimensions must be given as integers")) end ## Viscosity type @@ -32,7 +32,22 @@ Viscosity(args...) = Viscosity(promote(args...)...) Viscosity(nx::T, ny::T) where {T<:Number} = Viscosity((nx, ny)) Viscosity(nx::T, ny::T, nz::T) where {T<:Number} = Viscosity((nx, ny, nz)) function Viscosity(::NTuple{N,Number}) where {N} - throw(ArgumentError("Viscosity dimensions must be given as integers")) + throw(ArgumentError("Dimensions must be given as integers")) +end + +## Vorticity type +struct Vorticity{T} + yz::Union{T,Nothing} + xz::Union{T,Nothing} + xy::T + + Vorticity(yz::Union{T,Nothing}, xz::Union{T,Nothing}, xy::T) where {T} = new{T}(yz, xz, xy) +end + +Vorticity(nx::T, ny::T) where {T<:Number} = Vorticity((nx, ny)) +Vorticity(nx::T, ny::T, nz::T) where {T<:Number} = Vorticity((nx, ny, nz)) +function Vorticity(::NTuple{N,Number}) where {N} + throw(ArgumentError("Dimensions must be given as integers")) end ## SymmetricTensor type @@ -101,11 +116,12 @@ end ## StokesArrays type -struct StokesArrays{A,B,C,D,T} +struct StokesArrays{A,B,C,D,E,T} P::T P0::T V::A ∇V::T + ω::E τ::B ε::B ε_pl::B diff --git a/subduction/2D/LAMEM2D_sticky.jl b/subduction/2D/LAMEM2D_sticky.jl index 479f3bba..3ecc01ef 100644 --- a/subduction/2D/LAMEM2D_sticky.jl +++ b/subduction/2D/LAMEM2D_sticky.jl @@ -1,5 +1,5 @@ -# const isCUDA = true const isCUDA = false +# const isCUDA = true using TimerOutputs @@ -70,29 +70,29 @@ end function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) # Physical domain ------------------------------------ - ni = nx, ny # number of cells - di = @. li / ni # grid steps - grid = Geometry(ni, li; origin = origin) - (; xci, xvi) = grid # nodes at the center and vertices of the cells + ni = nx, ny # number of cells + di = @. li / ni # grid steps + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells # ---------------------------------------------------- # Physical properties using GeoParams ---------------- - ρbg = 2.7e3 - rheology = init_rheologies(; ρbg = ρbg) - rheology_augmented = init_rheologies(; ρbg = 0e0) - dt = 50e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + ρbg = 2.7e3 + rheology = init_rheologies(; ρbg = ρbg) + rheology_augmented = init_rheologies(; ρbg = 0e0) + dt = 50e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter # ---------------------------------------------------- # Initialize particles ------------------------------- - nxcell = 40 - max_xcell = 60 - min_xcell = 20 - particles = init_particles( + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( backend_JP, nxcell, max_xcell, min_xcell, xvi, di, ni ) - subgrid_arrays = SubgridDiffusionCellArrays(particles) + subgrid_arrays = SubgridDiffusionCellArrays(particles) # velocity grids - grid_vxi = velocity_grids(xci, xvi, di) + grid_vxi = velocity_grids(xci, xvi, di) # temperature pPhases, pT = init_cell_arrays(particles, Val(2)) τxx_p, τyy_p, τxy_p = init_cell_arrays(particles, Val(3)) @@ -101,8 +101,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Assign particles phases anomaly phases_device = PTArray(backend)(phases_GMG) - init_phases!(pPhases, phases_device, particles, xvi) phase_ratios = PhaseRatio(backend, ni, length(rheology)) + init_phases!(pPhases, phases_device, particles, xvi) phase_ratios_center!(phase_ratios, particles, grid, pPhases) # ---------------------------------------------------- @@ -187,6 +187,26 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk Vy_v = @zeros(ni.+1...) end + # Smooth out thermal field --------------------------- + for _ in 1:10 + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology_augmented, + args, + 1e6 * 3600 * 24 * 365.25, + di; + kwargs = ( + igg = igg, + phase = phase_ratios, + iterMax = 150e3, + nout = 1e2, + verbose = true, + ) + ) + end + T_buffer = @zeros(ni.+1) Told_buffer = similar(T_buffer) dt₀ = similar(stokes.P) @@ -219,8 +239,8 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk args = (; T = thermal.Tc, P = Plitho, dt=Inf) # args = (; T = thermal.Tc, P = stokes.P, dt=Inf) - compute_viscosity!(stokes, phase_ratios, args, rheology, (1e18, 1e24)) - compute_ρg!(ρg[2], phase_ratios, rheology, args) + # compute_viscosity!(stokes, phase_ratios, args, rheology, viscosity_cutoff) + # compute_ρg!(ρg[2], phase_ratios, rheology, args) # Stokes solver ---------------- t_stokes = @elapsed begin @@ -264,7 +284,7 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk igg = igg, phase = phase_ratios, iterMax = 150e3, - nout = 2e3, + nout = 1e2, verbose = true, ) ) @@ -393,7 +413,7 @@ figdir = "Subduction_LAMEM_2D" n = 128 nx, ny = n*6, n nx, ny = 512, 128 -nx, ny = 32*6, 32 +# nx, ny = 32*6, 32 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) @@ -401,23 +421,4 @@ else igg end -# main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); - - -# Xv = [x for x in xvi[1], y in xvi[2]][:]; -# Yv = [y for x in xvi[1], y in xvi[2]][:]; -# p = phases_GMG[:]; - -# scatter!(Xv./1e3, Yv./1e3, color = p, markersize = 10, colormap=:inferno) - -# init_phases!(pPhases, phases_device, particles, xvi) - -# # Make particles plottable -# p = particles.coords -# ppx, ppy = p -# pxv = ppx.data[:]./1e3 -# pyv = ppy.data[:]./1e3 -# clr = pPhases.data[:] -# idxv = particles.index.data[:]; - -# scatter!(Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv])) \ No newline at end of file +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/2D/LAMEM_setup2D_sticky.jl b/subduction/2D/LAMEM_setup2D_sticky.jl index b85bed44..3621735e 100644 --- a/subduction/2D/LAMEM_setup2D_sticky.jl +++ b/subduction/2D/LAMEM_setup2D_sticky.jl @@ -86,7 +86,7 @@ function GMG_subduction_2D(nx, ny) Phases, Temp, Grid2D; - xlim =(0, 300), + xlim =(0, 500), zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=30, phase = LithosphericPhases(Layers=[30 80], Phases=[1 2 0], Tlab=1250 ), From b3bbdceaffb78fd9c7ccaeedeba2a0a77b4fc08d Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 15 May 2024 16:23:26 +0200 Subject: [PATCH 43/60] few fixes --- src/stokes/Stokes2D.jl | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 9ee08f1d..8b39ba05 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -505,13 +505,14 @@ function _solve!( end Vx_on_Vy = @zeros(size(stokes.V.Vy)) - ρg_bg = 2700 * 9.81 - Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg) .* di[2], dims=2), dims=2), dims=2) - args.P .= stokes.P .+ Plitho .- minimum(stokes.P) - + compute_viscosity!(stokes, phase_ratios, args, rheology, viscosity_cutoff) compute_ρg!(ρg[2], phase_ratios, rheology, args) + (; ρbg) = args + ρgz_diff = ρg[2] .- ρbg + Plitho = reverse(cumsum(reverse((ρg[2]) .* di[2], dims=2), dims=2), dims=2) + while iter ≤ iterMax iterMin < iter && err < ϵ && break @@ -534,6 +535,7 @@ function _solve!( θ_dτ, args, ) + args.P .= stokes.P .+ Plitho .- minimum(stokes.P) # stokes.P[1, 1] = stokes.P[2, 1] # stokes.P[end, 1] = stokes.P[end - 1, 1] @@ -541,6 +543,7 @@ function _solve!( # stokes.P[end, end] = stokes.P[end - 1, end] update_ρg!(ρg[2], phase_ratios, rheology, args) + @. ρgz_diff = ρg[2] - ρbg @parallel (@idx ni .+ 1) compute_strain_rate!( @strain(stokes)..., stokes.∇V, @velocity(stokes)..., _di... @@ -593,7 +596,8 @@ function _solve!( stokes.P, @stress(stokes)..., pt_stokes.ηdτ, - ρg..., + ρg[1], + ρgz_diff, ητ, _di..., dt * free_surface, @@ -620,7 +624,7 @@ function _solve!( stokes.P, @stress(stokes)..., ρg[1], - ρg[2], + ρgz_diff, _di..., dt * free_surface, ) From 0b3f61068a7c150c1cfe3853f21ff8970275c269 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 15 May 2024 16:23:38 +0200 Subject: [PATCH 44/60] new benchmarks --- subduction/Buiter/Buiter2D.jl | 419 +++++++++++++++++++++ subduction/Buiter/Buiter_rheology.jl | 143 +++++++ subduction/Buiter/Buiter_setup2D_sticky.jl | 89 +++++ 3 files changed, 651 insertions(+) create mode 100644 subduction/Buiter/Buiter2D.jl create mode 100644 subduction/Buiter/Buiter_rheology.jl create mode 100644 subduction/Buiter/Buiter_setup2D_sticky.jl diff --git a/subduction/Buiter/Buiter2D.jl b/subduction/Buiter/Buiter2D.jl new file mode 100644 index 00000000..5013aedd --- /dev/null +++ b/subduction/Buiter/Buiter2D.jl @@ -0,0 +1,419 @@ +# const isCUDA = false +const isCUDA = true + +@static if isCUDA + using CUDA +end + +using JustRelax, JustRelax.JustRelax2D, JustRelax.DataIO +import JustRelax.@cell + +const backend = @static if isCUDA + CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +else + JustRelax.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +end + +using ParallelStencil, ParallelStencil.FiniteDifferences2D + +@static if isCUDA + @init_parallel_stencil(CUDA, Float64, 2) +else + @init_parallel_stencil(Threads, Float64, 2) +end + +using JustPIC, JustPIC._2D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +const backend_JP = @static if isCUDA + CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +else + JustPIC.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +end + +# Load script dependencies +using GeoParams, GLMakie, CellArrays + +# Load file with all the rheology configurations +include("Buiter_rheology.jl") +include("Buiter_setup2D_sticky.jl") +# include("LAMEM_setup2D.jl") + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_k = INDICES[2] +macro all_k(A) + esc(:($A[$idx_k])) +end + +function copyinn_x!(A, B) + @parallel function f_x(A, B) + @all(A) = @inn_x(B) + return nothing + end + + @parallel f_x(A, B) +end + +# Initial pressure profile - not accurate +@parallel function init_P!(P, ρg, z) + @all(P) = abs(@all(ρg) * @all_k(z)) * <(@all_k(z), 0.0) + return nothing +end +## END OF HELPER FUNCTION ------------------------------------------------------------ + +## BEGIN OF MAIN SCRIPT -------------------------------------------------------------- +function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk =false) + + # Physical domain ------------------------------------ + ni = nx, ny # number of cells + di = @. li / ni # grid steps + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + # ---------------------------------------------------- + + # Physical properties using GeoParams ---------------- + ρbg = 3.2e3 + 1 + rheology = init_rheologies(; ρbg = ρbg) + rheology_augmented = init_rheologies(; ρbg = 0e0) + dt = 50e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter + # ---------------------------------------------------- + + # Initialize particles ------------------------------- + nxcell = 40 + max_xcell = 60 + min_xcell = 20 + particles = init_particles( + backend_JP, nxcell, max_xcell, min_xcell, xvi, di, ni + ) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pPhases, pT = init_cell_arrays(particles, Val(2)) + τxx_p, τyy_p, τxy_p = init_cell_arrays(particles, Val(3)) + vorticity_p, = init_cell_arrays(particles, Val(1)) + particle_args = (pT, τxx_p, τyy_p, τxy_p, vorticity_p, pPhases) + + # Assign particles phases anomaly + phases_device = PTArray(backend)(phases_GMG) + phase_ratios = PhaseRatio(backend, ni, length(rheology)) + init_phases!(pPhases, phases_device, particles, xvi) + phase_ratios_center!(phase_ratios, particles, grid, pPhases) + # ---------------------------------------------------- + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(backend, ni) + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, Re=3π, r=1e0, CFL = 1 / √2.1) # Re=3π, r=0.7 + # ---------------------------------------------------- + + # TEMPERATURE PROFILE -------------------------------- + Ttop = 20 + 273 + Tbot = maximum(T_GMG) + thermal = ThermalArrays(backend, ni) + @views thermal.T[2:end-1, :] .= PTArray(backend)(T_GMG) + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, top = false, bot = false), + ) + thermal_bcs!(thermal, thermal_bc) + @views thermal.T[:, end] .= Ttop + @views thermal.T[:, 1] .= Tbot + temperature2center!(thermal) + # ---------------------------------------------------- + + # Buoyancy forces + ρg = ntuple(_ -> @zeros(ni...), Val(2)) + # for _ in 1:2 + # compute_ρg!(ρg[2], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # JustRelax.Stokes2D.init_P!(stokes.P, ρg[2], xci[2]) + # end + compute_ρg!(ρg[2], phase_ratios, rheology_augmented, (T=thermal.Tc, P=stokes.P)) + Plitho = reverse(cumsum(reverse((ρg[2]).* di[2], dims=2), dims=2), dims=2) + + # ρg_bg = ρbg * 9.81 + # Plitho = reverse(cumsum(reverse((ρg[2] .+ ρg_bg).* di[2], dims=2), dims=2), dims=2) + + # Rheology + args0 = (T=thermal.Tc, P=Plitho, dt = Inf) + viscosity_cutoff = (1e17, 1e24) + compute_viscosity!(stokes, phase_ratios, args0, rheology, viscosity_cutoff) + + # PT coefficients for thermal diffusion + pt_thermal = PTThermalCoeffs( + backend, rheology_augmented, phase_ratios, args0, dt, ni, di, li; ϵ=1e-5, CFL=1e-3 / √3 + ) + + # Boundary conditions + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true , right = true , top = true , bot = true), + free_surface = false, + ) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(@velocity(stokes)...) + + # Plot initial T and η profiles + # let + # Yv = [y for x in xvi[1], y in xvi[2]][:] + # Y = [y for x in xci[1], y in xci[2]][:] + # fig = Figure(size = (1200, 900)) + # ax1 = Axis(fig[1,1], aspect = 2/3, title = "T") + # ax2 = Axis(fig[1,2], aspect = 2/3, title = "log10(η)") + # scatter!(ax1, Array(thermal.T[2:end-1,:][:]), Yv./1e3) + # scatter!(ax2, Array(log10.(stokes.viscosity.η[:])), Y./1e3) + # # scatter!(ax2, Array(stokes.P[:]), Y./1e3) + # # scatter!(ax2, Array(Plitho[:]), Y./1e3) + # ylims!(ax1, minimum(xvi[2])./1e3, 0) + # ylims!(ax2, minimum(xvi[2])./1e3, 0) + # hideydecorations!(ax2) + # # save(joinpath(figdir, "initial_profile.png"), fig) + # fig + # end + + # IO ------------------------------------------------- + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni.+1...) + Vy_v = @zeros(ni.+1...) + end + + # Smooth out thermal field --------------------------- + for _ in 1:10 + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology_augmented, + args0, + 1e6 * 3600 * 24 * 365.25, + di; + kwargs = ( + igg = igg, + phase = phase_ratios, + iterMax = 150e3, + nout = 1e2, + verbose = true, + ) + ) + end + + T_buffer = @zeros(ni.+1) + Told_buffer = similar(T_buffer) + dt₀ = similar(stokes.P) + for (dst, src) in zip((T_buffer, Told_buffer), (thermal.T, thermal.Told)) + copyinn_x!(dst, src) + end + grid2particle!(pT, xvi, T_buffer, particles) + + # Time loop + t, it = 0.0, 0 + + # Vertice arrays of normal components of the stress tensor + # τxx_vertex, τyy_vertex = @zeros(ni.+1...), @zeros(ni.+1...) + # τxx_o_vertex, τyy_o_vertex = @zeros(ni.+1...), @zeros(ni.+1...) + + while it < 1000 # run only for 5 Myrs + # while (t/(1e6 * 3600 * 24 *365.25)) < 5 # run only for 5 Myrs + + # interpolate fields from particle to grid vertices + particle2grid!(T_buffer, pT, xvi, particles) + @views T_buffer[:, end] .= Ttop + @views T_buffer[:, 1] .= Tbot + @views thermal.T[2:end-1, :] .= T_buffer + thermal_bcs!(thermal, thermal_bc) + temperature2center!(thermal) + + # Update buoyancy and viscosity - + Plitho .= reverse(cumsum(reverse((ρg[2]).* di[2], dims=2), dims=2), dims=2) + Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) + + args = (; T = thermal.Tc, P = Plitho, dt=Inf, ρbg = ρbg * 9.81) + # args = (; T = thermal.Tc, P = stokes.P, dt=Inf) + # compute_viscosity!(stokes, phase_ratios, args, rheology, viscosity_cutoff) + # compute_ρg!(ρg[2], phase_ratios, rheology, args) + + # Stokes solver ---------------- + t_stokes = @elapsed begin + out = solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + phase_ratios, + rheology_augmented, + args, + dt, + igg; + kwargs = ( + iterMax = 150e3, + nout = 1e3, + viscosity_cutoff = viscosity_cutoff, + free_surface = false, + viscosity_relaxation = 1e-2 + ) + ); + end + println("Stokes solver time ") + println(" Total time: $t_stokes s") + println(" Time/iteration: $(t_stokes / out.iter) s") + tensor_invariant!(stokes.ε) + dt = compute_dt(stokes, di) * 0.8 + # ------------------------------ + + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology_augmented, + args, + dt, + di; + kwargs = ( + igg = igg, + phase = phase_ratios, + iterMax = 50e3, + nout = 1e2, + verbose = true, + ) + ) + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology_augmented, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # JustRelax.Stokes2D.rotate_stress_particles!( + # stokes, + # τxx_vertex, + # τyy_vertex, + # τxx_o_vertex, + # τyy_o_vertex, + # τxx_p, + # τyy_p, + # τxy_p, + # vorticity_p, + # particles, + # grid, + # dt + # ) + + # check if we need to inject particles + inject_particles_phase!(particles, pPhases, (pT, ), (T_buffer, ), xvi) + # inject_particles_phase!(particles, pPhases, (pT, τxx_p, τyy_p, τxy_p, vorticity_p), (T_buffer, τxx_vertex, τyy_vertex, stokes.τ.xy, stokes.ω.xy_v), xvi) + # update phase ratios + phase_ratios_center!(phase_ratios, particles, grid, pPhases) + + @show it += 1 + t += dt + + # Data I/O and plotting --------------------- + if it == 1 || rem(it, 10) == 0 + # checkpointing(figdir, stokes, thermal.T, η, t) + (; η_vep, η) = stokes.viscosity + if do_vtk + velocity2vertex!(Vx_v, Vy_v, @velocity(stokes)...) + data_v = (; + T = Array(T_buffer), + τII = Array(stokes.τ.II), + εII = Array(stokes.ε.II), + Vx = Array(Vx_v), + Vy = Array(Vy_v), + ) + data_c = (; + P = Array(stokes.P), + η = Array(η_vep), + ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + velocity_v + ) + end + + # Make particles plottable + p = particles.coords + ppx, ppy = p + pxv = ppx.data[:]./1e3 + pyv = ppy.data[:]./1e3 + clr = pPhases.data[:] + # clr = pT.data[:] + idxv = particles.index.data[:]; + + # Make Makie figure + ar = 3 + fig = Figure(size = (1200, 900), title = "t = $t") + ax1 = Axis(fig[1,1], aspect = ar, title = "T [K] (t=$(t/(1e6 * 3600 * 24 *365.25)) Myrs)") + ax2 = Axis(fig[2,1], aspect = ar, title = "Phase") + ax3 = Axis(fig[1,3], aspect = ar, title = "log10(εII)") + ax4 = Axis(fig[2,3], aspect = ar, title = "log10(η)") + # Plot temperature + h1 = heatmap!(ax1, xvi[1].*1e-3, xvi[2].*1e-3, Array(thermal.T[2:end-1,:]) , colormap=:batlow) + # Plot particles phase + h2 = scatter!(ax2, Array(pxv[idxv]), Array(pyv[idxv]), color=Array(clr[idxv]), markersize = 1) + # Plot 2nd invariant of strain rate + h3 = heatmap!(ax3, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(stokes.ε.II)) , colormap=:batlow) + # Plot effective viscosity + h4 = heatmap!(ax4, xci[1].*1e-3, xci[2].*1e-3, Array(log10.(η_vep)) , colormap=:batlow) + hidexdecorations!(ax1) + hidexdecorations!(ax2) + hidexdecorations!(ax3) + Colorbar(fig[1,2], h1) + Colorbar(fig[2,2], h2) + Colorbar(fig[1,4], h3) + Colorbar(fig[2,4], h4) + linkaxes!(ax1, ax2, ax3, ax4) + fig + save(joinpath(figdir, "$(it).png"), fig) + end + # ------------------------------ + + end + + return nothing +end + +## END OF MAIN SCRIPT ---------------------------------------------------------------- +do_vtk = true # set to true to generate VTK files for ParaView +figdir = "Buiter_2D" +# nx, ny = 512, 256 +# nx, ny = 512, 128 +n = 128 +nx, ny = n*6, n +nx, ny = 256, 256 +# nx, ny = 128, 128 +# nx, ny = 32*6, 32 +li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, 1; init_MPI= true)...) +else + igg +end + +main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); diff --git a/subduction/Buiter/Buiter_rheology.jl b/subduction/Buiter/Buiter_rheology.jl new file mode 100644 index 00000000..cc3a7768 --- /dev/null +++ b/subduction/Buiter/Buiter_rheology.jl @@ -0,0 +1,143 @@ +using GeoParams.Dislocation +using GeoParams.Diffusion + +function init_rheology_nonNewtonian(; ρbg = 0e0) + #dislocation laws + disl_wet_olivine = SetDislocationCreep(Dislocation.wet_olivine1_Hirth_2003) + # diffusion laws + diff_wet_olivine = SetDiffusionCreep(Diffusion.wet_olivine_Hirth_2003) + + lithosphere_rheology = CompositeRheology( (disl_wet_olivine, diff_wet_olivine)) + init_rheologies(lithosphere_rheology; ρbg = ρbg) +end + +function init_rheology_nonNewtonian_plastic(; ρbg = 0e0) + #dislocation laws + disl_wet_olivine = SetDislocationCreep(Dislocation.wet_olivine1_Hirth_2003) + # diffusion laws + diff_wet_olivine = SetDiffusionCreep(Diffusion.wet_olivine_Hirth_2003) + # plasticity + ϕ_wet_olivine = asind(0.1) + C_wet_olivine = 1e6 + η_reg = 1e19 + + lithosphere_rheology = CompositeRheology( + ( + disl_wet_olivine, + diff_wet_olivine, + DruckerPrager_regularised(; C = C_wet_olivine, ϕ = ϕ_wet_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + ) + ) + init_rheologies(lithosphere_rheology; ρbg = ρbg) +end + +function init_rheology_linear(; ρbg = 0e0) + lithosphere_rheology = CompositeRheology( (LinearViscous(; η=1e20),)), + init_rheologies(lithosphere_rheology; ρbg = ρbg) +end + +function init_rheologies(lithosphere_rheology; ρbg = 0e0) + # #dislocation laws + # disl_wet_olivine = SetDislocationCreep(Dislocation.wet_olivine1_Hirth_2003) + # # diffusion laws + # diff_wet_olivine = SetDiffusionCreep(Diffusion.wet_olivine_Hirth_2003) + + # ϕ_wet_olivine = asind(0.1) + # C_wet_olivine = 1e6 + + # ϕ_oceanic_crust_upper = asind(0.1) + # C_oceanic_crust_upper = 0.3e6 + + # # soft_C = LinearSoftening((C_oceanic_litho*0.05, C_oceanic_litho), (0.1, 0.5)) + + # elasticity = ConstantElasticity(; G=5e10, ν=0.5) + # common physical properties + α = 2.4e-5 # 1 / K + Cp = 750 # J / kg K + + # Define rheolgy struct + rheology = ( + # Name = "Asthenoshpere", + SetMaterialParams(; + Phase = 1, + Density = ConstantDensity(; ρ=3.2e3-ρbg), + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + CompositeRheology = CompositeRheology( (LinearViscous(; η=1e20),)), + Gravity = ConstantGravity(; g=9.81), + ), + # Name = "Oceanic lithosphere", + SetMaterialParams(; + Phase = 2, + Density = PT_Density(; ρ0=3.2e3-ρbg, α = α, β = 0e0, T0 = 273+1474), + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + # CompositeRheology = CompositeRheology( + # ( + # disl_wet_olivine, + # diff_wet_olivine, + # DruckerPrager_regularised(; C = C_wet_olivine, ϕ = ϕ_wet_olivine, η_vp=η_reg, Ψ=0.0) # non-regularized plasticity + # ) + # ), + CompositeRheology = lithosphere_rheology, + ), + # Name = "oceanic crust", + SetMaterialParams(; + Phase = 3, + Density = ConstantDensity(; ρ=3.2e3-ρbg), + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + CompositeRheology = CompositeRheology( (LinearViscous(; η=1e20),)), + ), + # Name = "StickyAir", + SetMaterialParams(; + Phase = 4, + Density = ConstantDensity(; ρ=1-ρbg), # water density + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e20),)), + ), + ) +end + +function init_phases!(phases, phase_grid, particles, xvi) + ni = size(phases) + @parallel (@idx ni) _init_phases!(phases, phase_grid, particles.coords, particles.index, xvi) +end + +@parallel_indices (I...) function _init_phases!(phases, phase_grid, pcoords::NTuple{N, T}, index, xvi) where {N,T} + + ni = size(phases) + + for ip in JustRelax.cellaxes(phases) + # quick escape + @cell(index[ip, I...]) == 0 && continue + + pᵢ = ntuple(Val(N)) do i + @cell pcoords[i][ip, I...] + end + + d = Inf # distance to the nearest particle + particle_phase = -1 + for offi in 0:1, offj in 0:1 + ii = I[1] + offi + jj = I[2] + offj + + !(ii ≤ ni[1]) && continue + !(jj ≤ ni[2]) && continue + + xvᵢ = ( + xvi[1][ii], + xvi[2][jj], + ) + d_ijk = √(sum((pᵢ[i] - xvᵢ[i])^2 for i in 1:N)) + if d_ijk < d + d = d_ijk + particle_phase = phase_grid[ii, jj] + end + end + @cell phases[ip, I...] = Float64(particle_phase) + end + + return nothing +end diff --git a/subduction/Buiter/Buiter_setup2D_sticky.jl b/subduction/Buiter/Buiter_setup2D_sticky.jl new file mode 100644 index 00000000..57323a93 --- /dev/null +++ b/subduction/Buiter/Buiter_setup2D_sticky.jl @@ -0,0 +1,89 @@ +using GeophysicalModelGenerator + +function GMG_subduction_2D(nx, ny) + model_depth = 660 + # Our starting basis is the example above with ridge and overriding slab + nx, nz = nx, ny + Tbot = 1474.0 + x = range(0, 3000, nx); + z = range(-model_depth, 10, nz); + Grid2D = CartData(xyz_grid(x,0,z)) + Phases = zeros(Int64, nx, 1, nz); + Temp = fill(Tbot, nx, 1, nz); + air_thickness = 20.0 + lith = LithosphericPhases(Layers=[80], Phases=[1 0]) + + # phases + # 0: asthenosphere + # 1: lithosphere + # 2: subduction lithosphere + # 3: oceanic crust + # 4: air + add_box!( + Phases, + Temp, + Grid2D; + xlim =(0, 3000), + zlim =(-model_depth, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[], Phases=[0]), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) + ) + + # Add left oceanic plate + add_box!( + Phases, + Temp, + Grid2D; + xlim =(100, 3000-100), + zlim =(-model_depth, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = lith, + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) + ) + + # Add right oceanic plate crust + add_box!( + Phases, + Temp, + Grid2D; + xlim =(3000-1430, 3000-200), + zlim =(-model_depth, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0]), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) + ) + + # Add slab + add_box!( + Phases, + Temp, + Grid2D; + xlim = (3000-1430, 3000-1430-250), + zlim =(-80, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=-30, + phase = LithosphericPhases(Layers=[8 80], Phases=[2 1 0]), #, Tlab=Tbot ), + T = HalfspaceCoolingTemp( + Tsurface = 20, + Tmantle = Tbot, + Age = 80, + ) + ) + heatmap(x,z,Temp[:,1,:]) + # Lithosphere-asthenosphere boundary: + # ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); + # Phases[ind] .= 0; + + surf = Grid2D.z.val .> 0.0 + Temp[surf] .= 20.0 + Phases[surf] .= 3 + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + + li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 + origin = (x[1], z[1]) .* 1e3 + + ph = Phases[:,1,:] .+ 1 + + return li, origin, ph, Temp[:,1,:].+273 +end \ No newline at end of file From bedd83145bfaf6d7f29cee1957e0878af0a74842 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 15 May 2024 17:10:03 +0200 Subject: [PATCH 45/60] rheology switches --- subduction/Buiter/Buiter2D.jl | 13 ++++++++++--- subduction/Buiter/Buiter_rheology.jl | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/subduction/Buiter/Buiter2D.jl b/subduction/Buiter/Buiter2D.jl index 5013aedd..409b9ea8 100644 --- a/subduction/Buiter/Buiter2D.jl +++ b/subduction/Buiter/Buiter2D.jl @@ -76,8 +76,15 @@ function main(li, origin, phases_GMG, igg; nx=16, ny=16, figdir="figs2D", do_vtk # Physical properties using GeoParams ---------------- ρbg = 3.2e3 + 1 - rheology = init_rheologies(; ρbg = ρbg) - rheology_augmented = init_rheologies(; ρbg = 0e0) + rheology = init_rheology_linear(; ρbg = ρbg) + rheology_augmented = init_rheology_linear(; ρbg = 0e0) + + rheology = init_rheology_nonNewtonian(; ρbg = ρbg) + rheology_augmented = init_rheology_nonNewtonian(; ρbg = 0e0) + + rheology = init_rheology_nonNewtonian_plastic(; ρbg = ρbg) + rheology_augmented = init_rheology_nonNewtonian_plastic(; ρbg = 0e0) + dt = 50e3 * 3600 * 24 * 365 # diffusive CFL timestep limiter # ---------------------------------------------------- @@ -406,7 +413,7 @@ figdir = "Buiter_2D" # nx, ny = 512, 128 n = 128 nx, ny = n*6, n -nx, ny = 256, 256 +nx, ny = 512, 256 # nx, ny = 128, 128 # nx, ny = 32*6, 32 li, origin, phases_GMG, T_GMG = GMG_subduction_2D(nx+1, ny+1) diff --git a/subduction/Buiter/Buiter_rheology.jl b/subduction/Buiter/Buiter_rheology.jl index cc3a7768..e03d3260 100644 --- a/subduction/Buiter/Buiter_rheology.jl +++ b/subduction/Buiter/Buiter_rheology.jl @@ -32,7 +32,7 @@ function init_rheology_nonNewtonian_plastic(; ρbg = 0e0) end function init_rheology_linear(; ρbg = 0e0) - lithosphere_rheology = CompositeRheology( (LinearViscous(; η=1e20),)), + lithosphere_rheology = CompositeRheology( (LinearViscous(; η=1e20),)) init_rheologies(lithosphere_rheology; ρbg = ρbg) end From 306477af4caef4ed51f7b3c755b02683077e09a0 Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Wed, 15 May 2024 18:48:02 +0200 Subject: [PATCH 46/60] missing interpolation method --- src/ext/CUDA/2D.jl | 5 +++++ src/ext/CUDA/3D.jl | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/ext/CUDA/2D.jl b/src/ext/CUDA/2D.jl index 74deef2d..f523b1dc 100644 --- a/src/ext/CUDA/2D.jl +++ b/src/ext/CUDA/2D.jl @@ -168,6 +168,11 @@ function JR2D.center2vertex!( return center2vertex!(vertex_yz, vertex_xz, vertex_xy, center_yz, center_xz, center_xy) end +function JR2D.velocity2vertex!(Vx_v::CuArray, Vy_v::CuArray, Vx::CuArray, Vy::CuArray; ghost_nodes=true) + velocity2vertex!(Vx_v, Vy_v, Vx, Vy; ghost_nodes=ghost_nodes) + return nothing +end + # Solvers function JR2D.solve!(::CUDABackendTrait, stokes, args...; kwargs) return _solve!(stokes, args...; kwargs...) diff --git a/src/ext/CUDA/3D.jl b/src/ext/CUDA/3D.jl index 8e7caab9..2ae5c500 100644 --- a/src/ext/CUDA/3D.jl +++ b/src/ext/CUDA/3D.jl @@ -170,6 +170,11 @@ function JR3D.center2vertex!( return center2vertex!(vertex_yz, vertex_xz, vertex_xy, center_yz, center_xz, center_xy) end +function JR3D.velocity2vertex!(Vx_v::CuArray, Vy_v::CuArray, Vz_v::CuArray, Vx::CuArray, Vy::CuArray, Vz::CuArray) + velocity2vertex!(Vx_v, Vy_v, Vz_v, Vx, Vy, Vz) + return nothing +end + # Solvers function JR3D.solve!(::CUDABackendTrait, stokes, args...; kwargs) return _solve!(stokes, args...; kwargs...) From fd65762cecec8ee6d9826ec0c3cbb811593bdff7 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Thu, 16 May 2024 09:55:25 +0200 Subject: [PATCH 47/60] docs --- docs/src/man/subduction2d/setup.md | 99 +++++++++ docs/src/man/subduction2d/subduction2D.md | 248 ++++++++++++++++++++++ 2 files changed, 347 insertions(+) create mode 100644 docs/src/man/subduction2d/setup.md create mode 100644 docs/src/man/subduction2d/subduction2D.md diff --git a/docs/src/man/subduction2d/setup.md b/docs/src/man/subduction2d/setup.md new file mode 100644 index 00000000..3911103a --- /dev/null +++ b/docs/src/man/subduction2d/setup.md @@ -0,0 +1,99 @@ +# Model setup +As described in the original [paper](https://doi.org/10.5194/se-15-567-2024), the domain consists of a Cartesian box of $\Omega \in [0, 3000] \times [0, -660]$ km, with two 80km thick oceanic plates over the asthenospheric mantle. + +We will use GeophysicalModelGenerator.jl to generate the initial geometry, material phases, and thermal field of our models. We will start by defining the dimensions and resolution of our model, as well as initializing the `Grid2D` object and two arrays `Phases` and `Temp` that host the material phase (given by an integer) and the thermal field, respectively. + +```julia +nx, nz = 512, 218 +Tbot = 1474.0 # [Celsius] +model_depth = 660 +air_thickness = 10 +x = range(0, 3000, nx); +z = range(-model_depth, air_thickness, nz); +Grid2D = CartData(xyz_grid(x,0,z)) +Phases = zeros(Int64, nx, 1, nz); +Temp = fill(Tbot, nx, 1, nz); +``` + +In this model we have four material phases given by: + +| Material | Phase number | +| :---------------- | :----------: | +| asthenosphere | 0 | +| oceanic lithosphere | 1 | +| oceanic crust | 3 | +| air | 4 | + +We will start by initializing the model as asthenospheric mantle, with a thermal profile given by the half-space cooling model with an age of 80 Myrs. + +```julia +add_box!( + Phases, + Temp, + Grid2D; + xlim =(0, 3000), + zlim =(-model_depth, 0.0), + phase = LithosphericPhases(Layers=[], Phases=[0]), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80,Adiabat=0.4) +) +``` + +Next we add a horizontal 80km thick oceanic lithosphere. Note that we leave a 100km buffer zone next to the vertical boundaries of the domain, to facilitate the sliding of the oceanic plates. +```julia +add_box!( + Phases, + Temp, + Grid2D; + xlim =(100, 3000-100), # with 100 km buffer zones + zlim =(-model_depth, 0.0), + phase = LithosphericPhases(Layers=[80], Phases=[1 0]), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) +) +``` + +As in the original paper, we add a 8km thick crust on top of the subducting oceanic plate. +```julia +# Add right oceanic plate crust +add_box!( + Phases, + Temp, + Grid2D; + xlim =(3000-1430, 3000-200), + zlim =(-model_depth, 0.0), + Origin = nothing, StrikeAngle=0, DipAngle=0, + phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0]), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) +) +``` + +And finally we add the subducting slab, whith the trench located at 1430km from the right-hand-side boundary. + +```julia +add_box!( + Phases, + Temp, + Grid2D; + xlim = (3000-1430, 3000-1430-250), + zlim = (-80, 0.0), + Origin = (nothing, StrikeAngle=0, DipAngle=-30), + phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0]), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) +) +``` + +```julia + heatmap(x,z,Temp[:,1,:]) + + surf = Grid2D.z.val .> 0.0 + Temp[surf] .= 20.0 + Phases[surf] .= 3 + + Grid2D = addfield(Grid2D,(;Phases, Temp)) + + li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 + origin = (x[1], z[1]) .* 1e3 + + ph = Phases[:,1,:] .+ 1 + + li, origin, ph, Temp[:,1,:].+273 +``` \ No newline at end of file diff --git a/docs/src/man/subduction2d/subduction2D.md b/docs/src/man/subduction2d/subduction2D.md new file mode 100644 index 00000000..31b75c06 --- /dev/null +++ b/docs/src/man/subduction2d/subduction2D.md @@ -0,0 +1,248 @@ +# 2D subduction + +Model setups taken from [Hummel, et al 2024](https://doi.org/10.5194/se-15-567-2024). + +# Model setup +We will use GeophysicalModelGenerator.jl to generate the initial geometry, material phases, and thermal field of our models. + + +# Initialize packages + +Load JustRelax necessary modules and define backend. +```julia +using CUDA +using JustRelax, JustRelax.JustRelax2D, JustRelax.DataIO +const backend_JR = CUDABackend +``` + +For this benchmark we will use particles to track the advection of the material phases and their information. For this, we will use [JustPIC.jl](https://github.com/JuliaGeodynamics/JustPIC.jl) +```julia +using JustPIC, JustPIC._2D +const backend = CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +``` + +We will also use `ParallelStencil.jl` to write some device-agnostic helper functions: +```julia +using ParallelStencil +@init_parallel_stencil(Threads, Float64, 2) #or (CUDA, Float64, 2) or (AMDGPU, Float64, 2) +``` +and will use [GeoParams.jl](https://github.com/JuliaGeodynamics/GeoParams.jl/tree/main) to define and compute physical properties of the materials: +```julia +using GeoParams +``` + +# Script + +## Model domain +```julia +nx = ny = 64 # number of cells per dimension +igg = IGG( + init_global_grid(nx, ny, 1; init_MPI= true)... +) # initialize MPI grid +ly = 1.0 # domain length in y +lx = ly # domain length in x +ni = nx, ny # number of cells +li = lx, ly # domain length in x- and y- +di = @. li / ni # grid step in x- and -y +origin = 0.0, 0.0 # origin coordinates +grid = Geometry(ni, li; origin = origin) +(; xci, xvi) = grid # nodes at the center and vertices of the cells +dt = Inf +``` + +## Physical properties using GeoParams +```julia +τ_y = 1.6 # yield stress. If do_DP=true, τ_y stand for the cohesion: c*cos(ϕ) +ϕ = 30 # friction angle +C = τ_y # Cohesion +η0 = 1.0 # viscosity +G0 = 1.0 # elastic shear modulus +Gi = G0/(6.0-4.0) # elastic shear modulus perturbation +εbg = 1.0 # background strain-rate +η_reg = 8e-3 # regularisation "viscosity" +dt = η0/G0/4.0 # assumes Maxwell time of 4 +el_bg = ConstantElasticity(; G=G0, Kb=4) +el_inc = ConstantElasticity(; G=Gi, Kb=4) +visc = LinearViscous(; η=η0) +pl = DruckerPrager_regularised(; # non-regularized plasticity + C = C, + ϕ = ϕ, + η_vp = η_reg, + Ψ = 0 +) +``` +## Rheology +```julia + rheology = ( + # Low density phase + SetMaterialParams(; + Phase = 1, + Density = ConstantDensity(; ρ = 0.0), + Gravity = ConstantGravity(; g = 0.0), + CompositeRheology = CompositeRheology((visc, el_bg, pl)), + Elasticity = el_bg, + + ), + # High density phase + SetMaterialParams(; + Density = ConstantDensity(; ρ = 0.0), + Gravity = ConstantGravity(; g = 0.0), + CompositeRheology = CompositeRheology((visc, el_inc, pl)), + Elasticity = el_inc, + ), + ) +``` + +# Phase anomaly + +Helper function to initialize material phases with `ParallelStencil.jl` +```julia +function init_phases!(phase_ratios, xci, radius) + ni = size(phase_ratios.center) + origin = 0.5, 0.5 + + @parallel_indices (i, j) function init_phases!(phases, xc, yc, o_x, o_y, radius) + x, y = xc[i], yc[j] + if ((x-o_x)^2 + (y-o_y)^2) > radius^2 + JustRelax.@cell phases[1, i, j] = 1.0 + JustRelax.@cell phases[2, i, j] = 0.0 + + else + JustRelax.@cell phases[1, i, j] = 0.0 + JustRelax.@cell phases[2, i, j] = 1.0 + end + return nothing + end + + @parallel (@idx ni) init_phases!(phase_ratios.center, xci..., origin..., radius) +end + +``` + +and finally we need the phase ratios at the cell centers: +```julia +phase_ratios = PhaseRatio(backend_JR, ni, length(rheology)) +init_phases!(phase_ratios, xci, radius) +``` + +## Stokes arrays + +Stokes arrays object +```julia +stokes = StokesArrays(backend_JR, ni) +``` + +## Initialize viscosity fields + +We initialize the buoyancy forces and viscosity +```julia +ρg = @zeros(ni...), @zeros(ni...) +η = @ones(ni...) +args = (; T = thermal.Tc, P = stokes.P, dt = Inf) +compute_ρg!(ρg[2], phase_ratios, rheology, args) +compute_viscosity!(stokes, 1.0, phase_ratios, args, rheology, (-Inf, Inf)) +``` +where `(-Inf, Inf)` is the viscosity cutoff. + +## Boundary conditions +```julia + flow_bcs = FlowBoundaryConditions(; + free_slip = (left = true, right = true, top = true, bot = true), + no_slip = (left = false, right = false, top = false, bot=false), + ) + stokes.V.Vx .= PTArray([ x*εbg for x in xvi[1], _ in 1:ny+2]) + stokes.V.Vy .= PTArray([-y*εbg for _ in 1:nx+2, y in xvi[2]]) + flow_bcs!(stokes, flow_bcs) # apply boundary conditions + update_halo!(stokes.V.Vx, stokes.V.Vy) + +``` + +## Pseuo-transient coefficients +```julia +pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL = 1 / √2.1) +``` + +## Just before solving the problem... +In this benchmark we want to keep track of τII, the total time `ttot`, and the analytical elastic solution `sol` +```julia + solution(ε, t, G, η) = 2 * ε * η * (1 - exp(-G * t / η)) +``` +and store their time history in the vectors: +```julia + τII = Float64[] + sol = Float64[] + ttot = Float64[] +``` + +## Advancing one time step + +1. Solve stokes +```julia +solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + phase_ratios, + rheology, + args, + dt, + igg; + kwargs = (; + iterMax = 150e3, + nout = 200, + viscosity_cutoff = (-Inf, Inf), + verbose = true + ) +) +``` +2. calculate the second invariant and push to history vectors +```julia +tensor_invariant!(stokes.ε) +push!(τII, maximum(stokes.τ.xx)) + +@parallel (@idx ni .+ 1) multi_copy!(@tensor(stokes.τ_o), @tensor(stokes.τ)) +@parallel (@idx ni) multi_copy!( + @tensor_center(stokes.τ_o), @tensor_center(stokes.τ) +) + +it += 1 +t += dt + +push!(sol, solution(εbg, t, G0, η0)) +push!(ttot, t) +``` +# Visualization +We will use `Makie.jl` to visualize the results +```julia +using GLMakie +``` + +## Fields +```julia + # visualisation of high density inclusion +th = 0:pi/50:3*pi; +xunit = @. radius * cos(th) + 0.5; +yunit = @. radius * sin(th) + 0.5; + +fig = Figure(size = (1600, 1600), title = "t = $t") +ax1 = Axis(fig[1,1], aspect = 1, title = L"\tau_{II}", titlesize=35) +ax2 = Axis(fig[2,1], aspect = 1, title = L"E_{II}", titlesize=35) +ax3 = Axis(fig[1,2], aspect = 1, title = L"\log_{10}(\varepsilon_{II})", titlesize=35) +ax4 = Axis(fig[2,2], aspect = 1) +heatmap!(ax1, xci..., Array(stokes.τ.II) , colormap=:batlow) +heatmap!(ax2, xci..., Array(log10.(stokes.EII_pl)) , colormap=:batlow) +heatmap!(ax3, xci..., Array(log10.(stokes.ε.II)) , colormap=:batlow) +lines!(ax2, xunit, yunit, color = :black, linewidth = 5) +lines!(ax4, ttot, τII, color = :black) +lines!(ax4, ttot, sol, color = :red) +hidexdecorations!(ax1) +hidexdecorations!(ax3) +save(joinpath(figdir, "$(it).png"), fig) +fig +``` + +### Final model +Shear Bands evolution in a 2D visco-elasto-plastic rheology model +![Shearbands](../assets/movies/DP_nx2058_2D.gif) From 6fa9be93e009bfcce66a8a844b47f54d87e6266e Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Thu, 16 May 2024 09:55:59 +0200 Subject: [PATCH 48/60] docs figures --- docs/src/man/subduction2d/setup_1.png | Bin 0 -> 64495 bytes docs/src/man/subduction2d/setup_2.png | Bin 0 -> 64705 bytes docs/src/man/subduction2d/setup_3.png | Bin 0 -> 64896 bytes docs/src/man/subduction2d/setup_4.png | Bin 0 -> 75055 bytes docs/src/man/subduction2d/setup_5.png | Bin 0 -> 75108 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/src/man/subduction2d/setup_1.png create mode 100644 docs/src/man/subduction2d/setup_2.png create mode 100644 docs/src/man/subduction2d/setup_3.png create mode 100644 docs/src/man/subduction2d/setup_4.png create mode 100644 docs/src/man/subduction2d/setup_5.png diff --git a/docs/src/man/subduction2d/setup_1.png b/docs/src/man/subduction2d/setup_1.png new file mode 100644 index 0000000000000000000000000000000000000000..13f5edb1365a0ea369f6ba784f3a4856c28ade80 GIT binary patch literal 64495 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 MUW=@MdGF!B0iqYMxBvhE literal 0 HcmV?d00001 diff --git a/docs/src/man/subduction2d/setup_2.png b/docs/src/man/subduction2d/setup_2.png new file mode 100644 index 0000000000000000000000000000000000000000..6b30c071ad54b8b107611f1904debe6d4500d64e GIT binary patch literal 64705 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 F_#0H&L$3e; literal 0 HcmV?d00001 diff --git a/docs/src/man/subduction2d/setup_3.png b/docs/src/man/subduction2d/setup_3.png new file mode 100644 index 0000000000000000000000000000000000000000..4db3da89cfc0d14b90e03368cc00ed6237f7f299 GIT binary patch literal 64896 zcmdqKd0dX`+xDHcmU&?rGf$NvDI_W>ETW=PU5L~oi7083q*}|gj8Q7lV2J86L@1SH zEJTJ%5)wj@OiAGcD~I2{bE0P)?_)kGyd&s7q#TyEnLl} zPmw#7)whIy478nLHcL*1YA98Zq`{d;0ox_r6>+r2| z22&?nc((X(rLE`QibD-$Z)jyNUhcG~Z7 z_v?rIo*Op5$0livyY2F4^Ue)yr#x_=UE7<3*G-vnb4r&X@9*f8Ykl=~HFlYtldyZo zYuCc`1jiQK1AngEZX^3ko5lLFzfAf~T|5?l{@T}B_LtwT?~wh)Z{X5jWPkf*w1Vs} z1OM*Tbg9XA{(rjErz0y1zSqFhn-vnnfY?jE$Eb*Eihw`#e(zIk<1`|(asf8?dr z=azZg3!gAIrhC&3#W>yA*4-#rWzX2<52Lnr9jYB}-14b=-ok5JI@u(w%r97#R(JX5 zPHv?+2AVZZi*5Ss;ZdVHAK$qYT@F=hovi?CA-g?H_yPpz}R?~>4@g5ZYnG<=fYb$_A@gxbKCai zi%ZL?`ugq8Uu}AO63y_eUg}HVxz+kK)Rd>$n3;9y-1$MunpLd8A=Mvs^*nBWUGK;5 zD~2d39X)m|`I%E)#zpJ$=c((sSu3kBJIluX^J3j0t@^)(tC2;GPcz2D-k!xu^SxIVv`np7#9u#zsFnVfpH_=gNYP9XdD`UaGsW`W1_Ctl==Du{3@rsQ{h?hstc3f{rb74?(^r*+?aOw_3PK~={4w#9z9!6dhXn} zT@yb3DB3ch+x=s+4rC1Q^=8LRaU4CRtolo)n(x;Gg-h)17X$}|g{|Ci>-Oy@@phT3 zd}|66rtg}$Z`9<57HNg$9d2gb4OD5|RnEtLTnwo7NpskAPxp}e6|-|$8<*bQf8+Y~ z*Kgh|U9#lE^VFI%8U05nEBDrNiQ?s$ZvXQ2YsQXVf#KodVPQJ9={x@Xx%K9a8&|S{ zpI%t~J}=JqO{l$B<;Q1d?M>=Z7TemsdigTm_3Nx7k6e1oK2iV2#pg%Xz5K~y!5qhA z{f1DVXTjQicTL;g<3>itd$%xSdlubiTS1w1lFQM;a<$>Z4UCM|RhLAEs4moAdS~C? zfB!w<udjS|azWz!V2$F`VhOeA|m)@$2-;ySdcrfT3lg2u>MP3%d+0TXFzIo$R zkZ_U<%i^E=5)M%1^L25K1?u)$AM)ZNjrW9wsl-M_MP0mTCDv)d_m9sGgoF$&nVVJ} zWc=XJ?_2)Q4K@7%d_*RJoLI;}hQy&t+rww~&j%_e3?83ZOc7Wh;?ThqRW zO3%@YH)a-_b??=y*N8bMcIWV>2v9{I)TcP!%E}6U?WS#OYwO-FB(r~;af?|le%AL;p=Uh;~9{-D+ z=39%$9vSbx+tq(Dvk8qFyy}c{aftl8m0zCdrj^YNk9S;9@%yr6?@lkecB$rLO78Kw z4Mm-N_l;UKg}|d&kWl#{U)`tn$NHM@TH#qK<(CTLRT^*ajN<~m-=|I64QQpIz%Z;7vEV z7h!=5-mLRomFVKTDeGA6m!BlcUwRlRR|ExBWv1QA|BzTW<>ZC;r&5AdEaM$=rSjIE zi;AvpDl14dnK5IAiAiCy`-;hv|FpC0H1XSgV(-wLb_%tJr%s)!^Xc->)22<+?w1+= z-1Ak3+~Kxb*ZLmnXFI>WoL{waPSMwzt_pYCwvfxx$}oC4rJwHbsrKdpBPIt0d}}Zo z@py%beNvgc!!$RyZz7C8w)G!reNl7bq!z(;;cK3+?W9-x>E}*5e^>^KW4>U=XlbZp zLAy3>+s?jw`0(K&#$hqNm)O~*Ry)`R64<0OzEpW9 zB_(N3Gceei@oLZBfPlera-Z7EcQzX&cR6X;;HW7v_S&u5xZiGb{;1{IrN+_OZ@>1_ z@jPvRFG8w1bZGSH)23#}=bZf3Uf%9%OSwsPZ(`fD?eMj&oO9M^i;Xe6riMDVs0d;Q ze|>W|Fd$%8|B2OQiG>~ejGLKT_B^%V)#rJ^?pIpMlT~U1);eZ9|4TC@`|TKOgV}@Z zq}|GuJIlS0ADozT(r{Y$MFEDX$8z3&d(yE*%tHN`k%Z@kDIOEOzP&5WJwCC&$<(PE zr9a39N*Yd2FBpZWay-+P_!P_!&wo14N4yp5f}+HcjXygrvCyX`K*@;1^8I5<`E%EM z6Q$?uGJ}bN2FXR6`*i8jh2-EeV-QLR~XQCVF!?i59D7 z&fF1o`0(Jg!pOaQ$F6#QF(=1DhBFSfQ^+}$lam81d3`j*y87J_|1s+7A_a_h^1ji1 z?v7sS$IPRRWx;2XJ6Uh|@Zs6yBAvA^*47bu?}-jOw{7dv7*^zrGup6MTr?bVX2`zq zWA^{H0Pephto$$E)#cD&8|&S#4Sy3a;>Dd?YHGik+WhCJIP2WtRjUIUU!1+!p2T^R z*4zCHSihJ#q9Uj<_&e(dRp0(4XRiL`Ud89v6O7OOm8xpLFKSrVQ-)^_jc(j3wR``4 zAWTg#nUuQZm$2DB1r4*ZmfC8GYD7-c^5;3Qq`MBCl?%Vb$o7Iy})fB z9?!P2dMcG1Cj(opD?9I$xIp%CFRE&dK?WYa}Np=~*${{Gvv!#Kw$ZX8B?d;5&^^ye;_Bl~H)Jx*Lz_*cvJ;_hE;=rm-=kdFPdEhIWs zuLH^Ld)HGUETT(opj$lOG*6cWAgh`;DL{YX$`T_5y>yNjTk(5 zaJSRTvyW-HzL}9+_2$6ay9eaEc2!t@XWwYGi=VCnQ0B&2PxP&?eQ;!Yd)ZicMgod_v*8vqT(r@JLq(kL$ud5Lgc--ZrYt%&M3`48*Tg`bzSA|-Me{ma`>8(htp)s z(q`1v;jE^`tr9nBtM1*qQ;+P;VXvJ&Ju3XLV?jdX`tSLB6s8+T==AE^)%D%|Lk}OC z8E}QX;rpq+4o=?iD#M?0rFlm#+2}i$(u5EjMKw|CMut@k*IE1U)n^bSnBT}nJDeg) zdzyuX1v&mRKiz+#bbo%r`Y*2y3KHD4`}=1!*VAFY@Ay4?7@SR0_-doEn1|#X>@wLih-7vmekV8=_9Ah)y2h4d-Ukh3671g-tX*X zuIPTHP1-P@G`;cT1$@=+msYr{AVIbSS%dW7o~x^=4B5N)U35>|`H4n8(JUh`o);u>x5Tt4Z!1`)Zo9WnGdio>7b_YOHvqGG>gK+4deIuU@;hef#z`WluDg-5)4f zLs5D4>J$TcE9XCDw`zLpU6@D7(U8)W0E|3}_R8|YhHIVl+z49|-gxxUqXzsJK7M!3 zx#ij9YTA+BEj#NUIB{sWGH415I>^ zNp9NVb8Xh2{|Ttj>ers9xs=+aAB!n?`J3eE!-o$6iPIlWlV9joU-kB%lmBVJvbirRbSs`T)!@` zAEj;W_(E@6%SYutl_|S-|JDRU0y@{Lo9KML4pd56SNZV(b=8{#tDLB<_4U&W8|o`h zoH(&}@80N0A8kd&sX-si}hkvOQVs*GQeMc|bg72*FxYoli z7rS&ApzF<-(wk`(uzAd&-#35zkZ*2AU>Or7c_x;0XPaQT2t5kSH-bO`scVSoC=t(7hK8Bv2KVHby>BP2> zZMMrMvRwMV59jq?1}Cxy|9?&D4O|vfQrLM&m{Ajr^w$n|vt4>sx2I%2O>6hScf^B+ zVL1))E-MW@{yN&w{bWPYBi|8K30G%-JZo$?`>NJuSt=O#V_xuyhJ^e%cTfDfpkc)C z4Vl#gu4-Lt`j5l3zO8a;^S0Z6WEOu>+H&n-+Kr;q8(shYPr*v#v-%xeqOzraWI9*+ z?LS^g?tdLe>0mm0_MV6cpAU~mrxgwuFrbi){^^r*?uXa60=IM;kah2#|F&%o1ml$} zPxx3YUAolED>?7IBOD0j29Zt%2mQ=@7Im|ZTT@?KMVVD_e#Kmhxx(^J@L&1y#T>gY z_kZtnBRyT3OdO$zyL(`KImffOxHx^^>({S~u5IDm>Z+<5M?a<{$~Muft9lz5sk!=U zf@5OAGs4L9{%cdAF28jck8&y`kHcIqMZ`O`^JsG7?4T|eH-eCW=V^0Y!m1G z;@2$mqoieOq#A16J+D1d0s{k0B6LC{B6O)sfrTpbP7fS3XtLzQg2a!67h@BXkZ$tw z_B%MWmWfW!R{4BA0OjiR?Oijtg68|UbNq8p@xc}E^Hzar7ACu$QCUHdJ4;Qf=UWeI zndp!c(@FQo<(^0Upln2V2_R{YTXc0JxX9kl?ry}yntQ(WUx5P=Yu|)IWX6~+T2vG> z`^dxWY$&scbsv+iY-qh@(yZFV>AU{8!#Sh4V%t~^7t3P$>3`k-x;yH=^ z`P#SP#Nn^tEk|g`pPsXpvkr{2Y$Vq@E9eh9=-W^aJ+m~;r_ROI)&A~&xPVnn*cJ#V z=%R#Gg{mdr3s$Xqdf|+8RQe8Cu?OMfx%97|vc*(I&Pv^;=azwhT6YaBjJ|L5BYT zHDzU&lxNfZ`&ZP|1?(T^C^f&HkuiPRw6!`B1AKl=maRz4l8uqP9F49+jrZn|j3#er zWuOdRQC1GvyLk6Cpa2G zzUhIcLLKJJo2SXf{}+iSLwlOP-_FcTggAzN+p*)=eE$yTpo})3^|qLr+6x z4~_6#Odht5n;Z1<@PdPbCdkwFTP(g`O8jZNnvA>R{j*gXNCDHB6 z>s#RS(86-UUvk~|f{dMg@(xdHFNs&lEOLGqt=YF@X1t-hxF7|L|d@K~YiB?Afye%%~86uGC23hohpdzxq6bsI;eXaVCYc!wzV! zUhL%LM6qxuR9&3xkW8iF6ur0i4hI17(5oQrI2dZ}?IRy(-7RtZ4#?3{Vv__Ry6hWm zyYya!V}S={#<7{94-;3}WX8K~{!6-iyUpc zd9=QsbXDOc0jT*HAZc9c_xX}N!NCbD@>C7xE_4t2sY}(Xl(rP;nD3)ukA_Vs>mqlzws%5zRxt^M(-h@0Ee-|J@2 zBatqIzr+=JA5q96)$NVpVXs}g)*Z!(2qBq2e?Q!x=chj;y>-2Rd@0U^)bLfEf9@S` ze(urm32RiXE|@-qfu?*@Z2WxFXw{_G0dNu*tW*;VPMWVQNVr>GiQ2Sp-!KHen8kI~ z741AfoXM~_i!MYIwKkhM^A1t39O2Tp_SFDD_I~Jh7Z(>}vr1G%Kv?$%?p<^UvKZb_og&MpiLQ zezj{ty8nPUWnwj@i+A>{@nzgFtGIRR)&Ri6Me9{7SAO6>Jxy?2AlnS;*AvR74_JQ= zeI>eU*REeboV}NR{rWrm%v-m5u@`v1N)zGg&zw0U-io15S{gh!WpJJe*PIPZ9F1_s zUao}=%4draatq2Xy*s@9wae^bAPDE1dlWX!U$oH> zj^$+H^M`3!Mb1xGXY^x_>Un&aH+k}8m;++QvSl$2_ep}GY?yHE3NB2s*tTt3_-@(C z+wZrtyr)}pBC~jeWY(l+FTlf+3s$G#Uf$XNsD2+8J?j8^@tA@HYIgKObrJ@p9olTs zucOn)cD#SDYxjYTTgv&-ykHII7nfhZUQ(B`tKWoi=q(h7^78V1Y=z>*VRs{Uk`Wre zT~0F`)Kv(nRgmBqm;+tLH(0H{(Ot;u3!A^^2Km(q$UxPO!Vn$d<#7G)&W8>inzC?I z6LNANY8=*cqL&+pmJ6{sWq_u3?io#7P%g!>GyD$;T>vMl;7!_1)Tdu*eSKw`Q7L8P zfpLz=p^^;{DYiQ}{f)ar?%+03tJs2|W^?ooi=RU>E8rqeQpKVgHYI)KFFz{=wTx-i zam(-jQs1wZR|S_f*8iJL~ zdZfDfs)~tTN z{nop;a^nq{M$QYapkQ53^5k23kvz;zCI;=_FI8^WzP-H4yitp;{!)<8`q!TKFRfq4 z&9{$S z==NY*yLp$qt3)P3%7=tDgmS9zt;_S(>f2G=)<<|b0WLATQ1)%^*zXAy4Z=U7Gued% z^o?|ZvSDChu~*rVpT$~o^X5%f`ywJFjM};vm;Z!J95r#Gpy79J-`+>l`JmJjHwNI? zQc#sw;He?GFLA>`Bj$=rLV4DsUGPQO>n!b%882#Faub#G$9E5opuWtxOig}Vj!gK+ zWxtZ_=)~pO`V|44rJCdk#8800;NMuQG-$+jp2!=->mRjW@5NcCdR_UeCGkJ;rk5=j3dTl~pXTekXwe}%OA0B<{a&N~m{fRa z{m#)+#MK+s74I>Lj97kkV_P<(g;HX{L|g?e+xLiyj`n4(8gDki=~*%f(EtQgT9}rm z>|XZ79qyp=sp~ypC+F1tu14OlKft0${2&L^CGOI6(u^55fg@s<6FMMhE8tPxlB{HD ztYRkb^<$?`t!Io$Z^QDzo*Zy2fS`YtvSzM3gae1nz{|@^Uw^YQn=t;|xtuZ&YW%y> zkNa1gGB7mM21)?@;*#F`r-M4Lo%CXa6e>MmH7>JFp>(>zXHw)G#Q#Lmip-n< zmb!oce*cMHwdCPd*~fO4Dk)feMlq7MB9#}Uu1k#KGRl|VSM=9(o+P{hR;nS=vnt2Q z$s^{T(((TB36ON;$dOL{b>^9w4UXkAwWo1gea0@^9T<3aU8Pg=p@YxjJxuic^g?W; zlMBWhP`4l=9!P7bU)K`P4%Dr~=a)AqG0f_0H}3DJ={!(Pt^E4duJ0e6T=#0{cw=HZ ztPpI@^cF{t9_4ui8R4dl$tS=KUB*A7YrlT$Yk#aZFpgHo>JW_DN^KR{4@hmrc*>N&n&=3*wBO)F3fb~eBKQU`aGHHt?4OU% zR#-lQqI>78so0^f=4p}{{;XL>M(^(*+ECi-i0c#hMk6Dm3iB}Iw~$^q@hqM}`#Tos z5;3@fC=-Zc_#Xm^I-W(F9&^f9f$hgvF*S6nv*bs03o`@fxXm$aLgXs!&k6b~p;O0YnZ3 zgc)*!Vt&FxYwJ_yjczKn;Uqc@^>h$~{=b#PUdZ1laqDesu^cubfPk=X@7}0Vo&Nr% z1CD5)|KrME92U4uj#ga^RfmAy)$*u{FD|$6&p5JQtM4ujY)SOSjT?XMGwuW1Z^W=+ zGg#hPv(TQX8F@>oW2QwfgOq42qs8&W^hdIw zy!IyJ;?6~_6?{N{1GrFZEde>OK#2tl&CKFg6H}dXHQA zn0vx+miFzTkzjpz7s3w39rm(CT{~zvVEwlTMA?2FIYo|=hMu|M(2zukO&d4v=r`dz z+t1k8cwBnSQlMq^+dW4k;a zg;l^>qOW;(q}%1omj{r_iZ~1!q&vy+IebM(-y~wmGvX=rE%w{DS>cTa47t+QTdu9X zH?dOK7(@X{NygqE@AyP97Fd;5og1fEN7nVkSO5^QnmzjgF_+p&T0dGk0k4F0Tq1Q2 zP=)MuKYx*BuuuQ!?5g87bqlTLeI>0I$%v2QR0H6Yjz&_Mogt21H9I?D7+2%LUDrTSQnQ{o=mv!_PyBnVXq) zL%OZ@@{8haQqG#S;3fBkA_j))0n(4PTQLsfQxI zh7=!nsoH^}0>2@o%lV~udmFHJHdNWN&7R7uf!kZ1E1Aolm=3!uVg=Zc0&{#z-aPi< z?W)PkkcWS6^{Z*>5{otS6INmw2n-4eii||Fi5qwy^Eed!o$v{jf(qrW^2#4j!(Y63 z5w6H7KmJBS8QV>dkS1NsJ&A%BhDOxL1jEE-5Adq42Ul@e4QI^QH^$D8`%`xR@OU-E zf>2?3Vqfk6MD)DKnYTEey>ZB~0BZ9U?#d=ufM@np$NR&bI{gjJF>$HI8dV%4?0sQR z!I*Oa879m)x>V2ydJPu~%6!Vs=XGou1LN>cBhO0EC}NQ%=T;Tg!~FTYJG2EdxEA(5 z#*9-UUOh)PHW|R1Q(nnJ*By{++0)i3r7S_zRGT-8&IdWUgK$neemZ6xJ|E&4#uGX+ zHi9?plvuNMXvEE|tcWpow|VNgl#j`*6!E0@SnO%`HdCiwhpG5TEy5`g4CaIhDcrqC zE>vM2)L9e4jl&P~hQzIqgxOICh`nK9K!d~;r;>(X(T;Z34i^gsZI%d`hauThCjtt~ z+C5j2>Q&*Q*;fYLG&g0uvV_sei>vf$(s_ucLm& zv1kGC-qa(Iw4aH_I|k@IFLsF)M-V~i-rc*7&o5R9l!b9zcDWpN>T;S0Vxu6CsRp(i zN1r8A@ZJe9;aFt22epzd1AKadBmws0V39bHje!V+8GnLm#OfcdEavJW%j!;=Dn@C) zkSn=t{AR z44fXkgm=L@ZaiMBMwyY)bA}*il|^7BHHZ+)Yv;npywYPny0v@Ey%9_)%w;OSpJ7L% zhPGRd+62ePkC8a!#~+O%J;VlJCgJ?=*H|Gc8H$F+y-TJ8v5886X!Z?e8|&JGB4-&} zH7UM+%k4Vjy7zc~GvmJ*{no#K|6aX9Lj?0Hu*dL&jlNkp{qRQQJq*{50knt6oyKsF zjs=8fxU46lHYSn)*mDPt9s3OaDi__}59k9&J1A{IGZT$k?2khBK{)I!hjH zEv!t-)6#p zJUF#j(x$!U@LP5|0X}!Oj#M70Q&cs0X8kG3ezM#GN}z_?dkr+D#DY8n$o74^cdvNm zuXpU|Q6kvvfQ(mw>D}zd^3AQRguU!zK_bqx8E~~B4r@5t0!_(I=G#^j{f=3V zQBV4*wv%kS)-Japex*@xeINF^9?dtDgH@suz3_4yuTsY%Y3!Mihxa>SUWz z{++Nqn_lAPL37C4{zuMAdArcR+8zGkQDfF{@~Z_agx13QAi3gVIg! zwU4m8=y}GmfRar~sTeLq^kX!JVY$*H@ChRqCR>n$W^!jg{q&PX`z_ai zL5q-D2x0=uOL0xp)D!R0kVu@E6K&hQ~ z+F4GK=-V%lU1=1RDRj7hpYg^biL#X%*xKpo=}noi9>x+Y)b49VJ@tIN$)TI}_pJJp zumn2s1>e%Iv+DlAD=e2_0Ggus&nlViI(0HJH0 zy}?PTZ$Rotj-X|3+rE9%{GPdoASyglt#d!JN>!OQ_3^|X`gLr1b-{MAf7x071Cu9B z?9#jU6UYX1235=W*Mz&S4vQ@sk-V@@Bsk_~y{|B=1{Dze+e;n-?8#(F%{whe0TIQL zM7gnTS3y>a!0=59WRjxf4EaILHZT41yh^raBsJyJHc`{$230%p^?!-)x6(3O~eTzxdJ)C3InaF zRiC4b-HVv`Z$5yS6N3JCgbgf}Vx+-0Eeqpqf|~7kPQ7e@`uS&x0#NmOwM#x6hXx}*6OK7x5B zDx8>Ki{)4{i;pxnH~;tFvfIuw!SQ{eJxAZ*pCy73{!HJ;w@B(&f^n1Dc;y z4y}$af5wczrNSye7=Vs6!<2KZTB6fDPW12;o2JG#(nSP`{Nn~vD5*=I4B}QbvefYQAPUOZ@-PA)bZD? z9yWBSJ&YtYbHI{}!9O(#mCoaDibk7$I2*K@W6O$ltN8()otW(8x z88ARicIh8I4-p)dSeWOiwYdJabm;S)JrL8oAfb^~U||7J(%Z!@*jt8keNrJmpc=A* zJxl-zz;n<=61aD7x4wO?EG)heVyB*Bt&-eQD zVzL37o3yiEP)N7=bMM}oBrEJdDF>LEo;`Zd=C_x{l-;kzs3{tF=6tc7{Iy{N zc7$T=7RP(Q%C~IUGJuX&Ng#BAV9WV8H;!REV8>{z;?ndo4-9+#v|JQP=;x7@>H+HX z4Oq~3)}QP2YgfF_xSg z<3Y^xg#@GTKoUs%WYcBjO#OT2uCR{FOK=Qt6`egi_Tt4_%py-I`6Y7~R$zqAY`F03 zBZ6TS+f-^WY0_nk(lU9=&+B?jeZ1p3WJmmf+T9Zi)>DVKYTa7E(1srwdf+6%ne^^0 zH9wUn8X3(h&*)4yz^}Ig*BRy@L2d;E9F~euJ8#}F^d^I**!+h@ zoM@wq#xGS$)oZGe;An!jiy;)sX5M)RiKpiUJ4<{JlnY<>4mXD-1of_a=AIWD;HVow zP2f;Ps|cP@QDv$1;cV=-MqPP zoa1~D3~KOejPYzSQo>V6Ah;oG-C(G@uYD#W$_PxUjY zr9y2x*b_pD7rh;@>)9Fo*R90m#vhni(Nc(YDhjqc5i(FZit-+%&=>fQVd&R(?Me|C zvP){g-!MEup`lP>>MpNmG`*rO3boooT;nRjIR~>3aXns9F$%K=y~bu#36Tx8_7XrS zrotRb{g2dw=$&kiU^l2P3}Bl#ZK^9<<@^5enIp1yV7j%l@$b&gpF~S`=gy#d>6rA3 z$U*2oz>)PXQIEr7ce%!E7kf?R`WN@i{6G+zBLD&gPRfBek`}}EIu{g%aG>g zpIJN&VfEkqXWD_DyJgP;f?myUHeonespkFNS;1ma)t6$u`X5J}$Nh6PkFqBelm^{l ztCWjWnmiT%;H60u?Fzm_;C-b1rfW#1JAn>o!j-h9`^zy;aHL8dGGW33jjh|a7a)%_ z8RA7@6JRVI4xyR){RyI2E=_k=%RJiq+><@-!H9U3_jGLR*uK4M{el16%KgtmxG+O+ zZ*P3(yzNce_DwO(oC;~zKP&XYLM5|HR+lDM#{a`x`=9;n|F)Mpw4cE9B;n-zUw-*T zh*uJ<;*czFQLCS$%j3@DMTp)a>H%pFs*tKCCfUM5^xKhxl94V5j7@>`b{olBoe1>O zGIFBXES1b;>e}btUuQ|aU<26ESIY%rP&$h8l0rie7lL<}^!K+1F=F4<#^RxTQTFA1 zG(EFp{)rA!AcmgzkJEYYb4&6$s=ytHoA)kBM8iBn5rV-;peJ#ek61zJO%WAqD15-& z%6y?!;|=NBx9>%^H4yQ;2vM|`!oGt@|4DH%AoBO*^g6&WASOV-wr45h zWww7voFm>ss;0c{2SG}hw5C1-9C@RoGc88RAkWJ6t_aDZc>*{?jX=xAOKwE8vQotT z3xVcR_=!G6S;5Y8l*$vXP2hg^M4>x8McR_yCr{(FEB&)||9MbySQUh$;if$g1fbjg z_urndY+bu_dHu>I3F~;83O))FGX6$^JAVH8XBj@yx)&fR9hse`cE9t$90mOt2TH&I z?70x$?#kdYHC5G)?b;CYBwbGB7vafCM zYnN6APh4~s7Wem$R-8HLs;{1cMbMr-gJO}Y3G3QHpxqh5Jq)OqNJx@M)->l+e-SyQ#&iIExV7AXo!T2kk!L6%b{F5V~-|L|Ba zvCzt(^H4xm7I?77i?POHPNhp$`WGDp@Rm_*Pr!r3&TaDWEjcHF*g8;uVb&TzkH329|N3?-5p&M+c(sr*`<{tzC4=+LrklB1>{^P0U% zwhCb2#X}HkAEyh5)>mGAK7pJ=M2F62hkAlhIesd!j9C$!=O2F@ba?5Jq7(#zz^8lr z?%kjzmUEn)PfOeW`l}n31js#$6h0PU*>|Fsg@&-GdZjj&1uuR*?YQ7_{vR6+0YTic zVntcO8tPh>BElOPf^`+0&+K@A9H-P6%F2fJ8!}!AR@CM7-wqE?FZ%u8A#G}X>_kz< zDCH6KK?d7V;i*|&xSvo4siLW^J->V3jv2UF5*g@_V{RfG#2AMFZ`yrO7le}ll0O&6 zoi9@xWR*+I^c|&YU|t9^VUdUqAfe)$+Pr!5xX-gk;zNH#P&;$_^e3EBMg|6>(-FAd zym=EG5^{V9%>ibnrlu?rYGxaXg>^dpB}h9Ekx@5q&d60AIPf5JzjWHtrN^4*9Q}G) z#fT-bh@kQkU%6f;kq)(|+Mw((G%*pHkv32t#2;4(_oJ&3M& zV2s&V627049Z0{_P+0)97$r?p$)pZ_fCl2OjNz7fO512p^&hPtF!~q*i%?dX(F9cH z-lc|gTkS>Q>d|9JELF2pV#$z<%n|s(0U*Hw9b~O|wK)9)SAIR3a!_ z3JPdm6l}vPD?~nIM*Kgp4PL08l3T*ij>~dEa4roJRn%mFKGNAKEP>S6;C}O#Fd^vu zhNWy83?BcA@=NX?oNRtd84OPoe=kqfFp(>EEEq$l~A7MNG5forIluL4A zvqc{*Ts+`=Auc0`YPL-oKYs};sajfXJSqoot?Q7PNSFxr#HEDo_Z1?>Nr^j18TKzszdj$3STYT|8ShDa*M(+L*nA24qr zhN_Ubu<5BQD7*rBN?K;dtEPO2#|FyZSAd zGv~XQ2@&g0J)41rM-y!zu|USx%2^P5;qh#ER4fV*AsK8|%G_URVk}4~;}h4jf=O1Y zlEARA+9qcuREidJ40ss$p>cQ_1y`{D^5x60uL8vib0v>XHx6cAR<9u1WPok6QJAST z5ARN=L=Cnpv4Dy24DmOXxIBHaXBm#n$W$;lcNf66hA;3)tGCg`EKl?}`VuLRfYCu8R z91V8sIuKikie*4hQ1NF^oI1|@n{w&Rwh^afnj8h|6b-D1y@;D&3Dx1FM%^#-cndto zJq{|u2G&Q<=b|!snVQ`jIutCU*_S{c6}1EjOK943`=C$ugKaV-m_~DdV(M8yW^ubV zZ7^#H4sGtq`5N%HP=_)lM=pp^O$iM)5p{r5;yQ|8!60wlnvYwCaGM5xCVhT>Xru-! z7^i%P$pjqu(~c>$VF^z-H^(#uJpc|FU_AsI;T(mD1=( zyj7wbR2k#3B>7?=fnBSUtT4~qVa%Kx1etVyd({h<%RFc*LK~@)9zWGI5W{M1(=#Ea+sjue1;Q-en=a~IaFeYXU zOkq%4X0f*Oi%CK+;snL+=TSk#ux1PqX*!YQ(<yLD^q(sU36Iy3x$jCw0TG^mOO~ z?zDqIss#yjHlNHkwX!<#gl}C5V+DSf8ZZQ)*?cSDt46`}=*3krSE2l=3HGicC9&gZ?*YxF8{oJ_6dd!6;{QQ9WK$y1zXuQwH5gFbAjc6Y#kHenO(EMh3#<|tJV5Ju;0i!Dm zvTon@!ofhZiev)DMmps22QM(gM-|2O^lFPbNsm!A(OhH0TmaD+%sy?|y7hE@eZcmc z;^NpLLV{?bp6Ozp0rjls^({s^{QDmIsR#apeDnHtoG!cw%Bmqox`IT6Kz|Zt&{fiv zVV^EZl+U3D4vZi|j?&fj0j+|fAqe7CBY%rr>x}jVfY>CE9SX7-RXmxEP{5 zwoBvK$2kT#L06|d6BnL!r6&IZ9kkDj8X2av~> z#IquOEoRd?3LP6RpoyX+j0JFxDn8_YMd*nbyBs##h4Nn+YEih>5*+d7il*cc6aE7* z+oSyXL_#L8+Rsl;vX3E8V6VXn3IYpPG2E}fF;O%UVQz+k?LX=dKV`9n@F@q1F$f^) ze;rb$IKbGFmufy|3_v=0Lob$0FP6)_j5=9H#|=%Qfp^?Fy6bF>Yk zunJBTO&9U?K0aIjo}*u4v_}>S`lVH?EZC>v!wY#y#`+f7TYiR-IQzf}n zBIj`Q!ml|I0q{5_IJR!xy0SEP1(gTvPxC;JF;ixtOgh8i4bXIcfl;1F#2IpGY!|nf zh1S5#9ibO9(ri3`#*F!b>kb0#PlpHW@lEaJ`wO;W3G68uG2A#>b}aD#I4s(+{LPW< zuDyHH>>QOH{Gy3CtsVS-HiPPC4(JN@H+{Rq)FwZnY5G;IVZzC?Hxle{LOxFLQ6OA- zkp-B=l>{t8r6EoJfm>j%)#^>ATbj$1KvKr8o}+OHFYGP|MYQB~91~gHHzmBeqgKq> zBUD#{O?dT)ykc|}9XP^_in&mXb|5)78`w(ld<%O5lLkE3*dk^P&oVRF! z1_Q!E&4p`CCOZ+Nl*88Q$0V-2B}?0B!6(nA2dmT#U0J7%%e#ufd`izSG*g~RHj#eq zBiAS35+!4`msj;>D{fS8V(sN5OblLwxJ0eVb4p7YELQUPjBvYQ028Aw=Lai=Eo!!F zIEao26yFJM?+%LEP>tqy(Nsm_3u#$2=w91l>Ys02XY;SQ!oBko>IW>Szx=(lA^uw$ z1Aj9UDjFVF@BHvINeqbcCi6(OaNa4|G5F8UZraqTZS41TF6~^9kZK*bjD+Y|Hyg79I__^n)vk*2ARcJAah@pX(FHbVS$`28z`ITp6-UZE^F)=f?TpQGQw0zn#kpq?|z2}x|hqOD( zcRgv7kx&LBfVtu+lVh&>YANI>Ky*BB97D#$HYhvU?Igaa@M7RK=@Jm`{)PcJv(s8e zXA>O6z&?n>-0LN7`qQTit)=4c(Qoit$~S&y+Ig7j+^w)sb;r1CrZ8!T6K?~6W|BaW zwK{H5V15TT+;sZ4+F7n%y*hwAf%Y$~c#Rq_f-o{~d;5r{j3dQmSO-(Ij|~$TDAq{| z(V&R5jO(e*zrHOQ7myOD>)5elq62)c5($-3VM7$be9Oj-|MokXH7WXWAvS@`R$y^V z2v_OyCEgDy!OA6Hy0Q7galIr1CV}TQ=w}zJa7EnUpj#_fm4#tHTvkAv_kV98|f{wplB&x}v5YGd!;^dSGv zn4~tibVjomK=v;Faa{d>t1?Aj!7_-)zWSta=y6V>Gu~Iw1Q)K}v?rXEDOXDv#x`Zj zRi@(~+)rX0&zEM8#7zn~z~k_mQzYy~)OgVM6sEWf3rL=@H+ew+A;2Dj6^6O>9v%?$ zD_J4}DTCIHa@RDyAFt!_quGC{Mv$xLTF=iA!v5&H71Jl@r4*9{2&wi-)kvv^$FaC zC&=O<2w>GGriA-I5%y5C1`}JDG>NPS_u~TX&~wy4b@j7RQSjy=At6CQNqDiFjZs8nL85JnVM(kXdx*W3-zwiqDI4PF5^A}*fDely=PWH z9ttF-8N;QbaGOmr1D-I2Mv}{d*|1(%f$0yD0=uQr&0%X6)dBqD^eWHP=McM~*D2?J zP(Nawp(~COL1-j7!n7i+jEXh8#aDoBB1|zMnAaod<6&gA`mdi`i8T-tLTWWpK;TNw|PGiNM z&RLrY@ywPH2$%$>t9yy>MJcGx)#-1-g_$3{ZCmrS&e|TNBsvrtdm_${*0(oXy7ZXO z1UN4SzM{Hl;MkVs!?gJfrW4b=@S`NLExHpvt07{dqoRa$j$EzFAr0t8Yee6^Cma|q z^jg$et2{gsq8O>fu&f!dFs8G}_DWz+hQmOQ=T6s;afRaTg=&F8hu5go%a&*`0goX? z3p|?5AjoJO(ce6E5KtO_mq3flz9Xrh1$Fyl6-~PY+F#@v&hxuJyiy6#>H9zsVhpYJ zHD1Z#!oPW78h-zMCbn^e*bjgNVT6r4&3KARN@AKwwrD?q&q|}7NBfPKSkP0Uaa`-Q zB7lK(2(+NC@W@&)Z#`pYb+ojNrB7R^}H+xBv>~x*r}`xzDHlpgl#}Ur`v5 ziB|3q-a^Wc0z6Vx-^vOq323h{cvUf|;4YnT_~eOvztuA%=y$9uut(^5vu4f=LqDfH zBsUB0kK{yue>G}8Fnczky9RGe5z?!o%fC!m23>3J7c# zb2ob=>!)Rf4M=m=>?_A=7crUvPJm`rLTwUcUbTsqw~FPJ7L)%pwk>I} zH*lF3tPJ7MxJnL-3lfw)9TzlgaeUu!TCvgjUurtT&!{1`tDTZ48Fy{pp55zFg5!eR z!?=P-zeNNH@P-VbpaKkw{m@|BN%!ChX?bcVU*DP!p^KD%4WI1Qx%+RU+9*Hl)+ffm z^osQuInbJD*ruoLsIXq-P=F>b=b71w(UBucaBkimE)S#kbAbr ziVkWUK7aguvuk4H+t}Ex`bYN6n7R%Rfoqll(J}-hCrw$iVH@VmIhM>AU_*NQjZ94D z&ZwHQo|*hq(#fKwzW$|rn(0W&(TVR*oE2rSsPu_C9AJsd%rUnr2g?B7banNzH=)A^ z_4W_#*k-6!%eQ-b>)fL&csC{Gsd*~FOxEL992gK4xQEP}H}6N#d-OU?{cMks^Jh3< zgX2z^Ck-gK2w_CMf$;Y<-3?Rh<|n(wMv;z>MMtkNi4JqQt>CXCX2=oH&+!WyYW|ez z`*oMy2JTzZF3ZB#!NK9>=L=CZ4Cmg^8akY@WOo$BIKGGQ5f}ps;b_X1Q=JB&kTbe+ z=Dl~s(pVvO?u^1C#wf!X*w{?qu2~8(@dHvT z_hp!vWUJJVxrk7;0o}Gv-KAUoj-i-t7<;j|K7_3S_Ya2XPPaHesE9n}xXF++5pz<~?v-nwe) zDEX1|6-)M-8_%8sO}k6;<=@~}VRZ?yaxozEdMr5o#}zWD;?ACfz z8%yG?WXzR$TaGTCohsH&3^J?7Y?=a`7L(q@5KZFG4{(jJb94GvBvNE{=%@V+B1o9X z*TG65&?F`^B>Zv=& z@rzYbm?NcptK5Euk?WSyGK9a9^a@C4@%l-CGzqv%16v++fxh~!N` zn228jf$?ymM3M`+CA^u3-n4Ish8z>)?PV)1T61Jszo9ZT?AGvMf!RLfyEipHq|nlb z@cc-sPl<`3+{nO#zufzZ>fk~a>g%2}TY96i4$qS=nC&e8}iX9cjZfs@DzT(s};QwhP-S6gWv$%=R2{e2n#3f z9N)U+=ux}$C5^FUpyjD_$xR(~^Sag55$0xL-y@j>d!2p$WD8vIOi~AuFKY6_M5l1q zQ@CuW_jAtyTJh4)h0xz1em@dSlkl~pFi>>@SRpzOvSE*_3h$*DwRA5c6^@EDvZ14P z46le?w|(m1b`xx3)Edv<=ZtF&1P#!wu z-Sqbn2L}wDFrnu9FLl>{Oj*C2m!;Tc>y5>DB}gduBY-7zRVX9B8yoHl%#C_mRT3H? zi&#sNdLPvaEe*L98ur&1twAc@hc1bLiTn-`76xKMDvzEhhSl(fHO^wma`1I-{#mVl z!;D?pWwl*YL?{5#>eb}m8^`bXLWmLX7Ofx{KJfR4lUw)8A;u`(Iw75WDrApR^J5(U z;vL#nbCUO={tH2+g8QUgr9sM*-}tdCkp={DF!s2m_p+z)c52Orz^0&*S#o)lR-n1* zkf9qlspT92LqxhgG!WJ`Uh1pQ!gkhw>r`FvHxQEadKOfg#;-~W8f!l%Jp8T3&EVz> zNop>so3C#Sn{C?L#7u`yLa6(p^)^f$JDNb!^c@FAap^}|b4^V@;qmi;_-=d+axpjL zOXmXcgpYuCF68+_5d5rJv-l#3#6wIoAf7%^yL0^3t*uMm7!GOt!GK~1@4&Uv|BJOZ zfvY)x`~Ne{*haRDWwJ!;C{dP5MAno_3fb3?Eh@@Vjmg-u71iO$mNq-tl4a~EWe~

}dXT~0c{1^z_RkAYyj$lzlmNoL?d<{t;N3{wEs1Dy0m1HQ2m)sL$AK$VaV@-Xnp_W}e$_7k({3ECcy zWdU%q2`%D_ej+a<1a_f3cc7*SrW53vPHmaLP*C*+yibQrQk$5V05#Aa01mDio$i<& zBus{90NXN&9A9#y1I$W<61N3AryM!p&e>Q60&fcx0LYuG zG35aK7w}OAocVhoY}rIKHnaMdQ||JSXx*h12oi@ z)%)tzA_~89)ZwonmBkb<+%c3EP!XbiW}IuQ$p*f6BKd8AUjc;ij>?MsuP3l*3{V+Y zug-t_^3K}Pr(oxzrai(O`_7Jz)#m5~6y*djpOaw9_DlHT`SZ2_I=h)&HL^es^s14a zKdxB@i7&|K=XB|;7w~4=mBPaNeZdQ?kBx}+;f@Ha9TEp33%UImT~V_v2l@G2B3jC6 z%q`L>+e@N}=F{4}dkKFzWytQ)d}7a3sczXKUmaDFvfT6aia*Z) z4Ltx5HIlQX=g%YUNJ{R=d|nz)Ha9-`P4DtkP@a&e5}!A{S)0Vqk-uBwiqfQuyZd0I zXnZ5cVkRrw_}D^$Z(ni%a2C)E^YEx0dW*UCUdS}3@@DKKQ?ro{td@1~2d_nC?}=#= zC*V`Uk<2StOV`$-&@`0tMak8}V{NH}d?WY#U|{PU26z4-(N-#BL zdn#`lOV%V?^=GKoTPSpdP;IJP+@KPLmEFu%#@m*W^l02R8DB5~1OY^p4oEcsTL%Cr zGTSJbh{@{a;M`G8bMyz3eqd8m)4O*r*`RXN;t9jl^ZbkV?%gw!W`NRks8(;w%geK8 zJY9PsWp=%sjI{h*r3vVXH2T9=lwKG7A z{=fgy9Qt0M$>hPpnkky(iM$7^}lAqr_gF2@ps zi1D(mL_8{|h5)h7FdL6p(>4y;A)nf%rk^)Sis-xoA{rT62%#;AJ82N@$J+CX=`)Aj zF^mL|#*G`#ojwVEDlwAjx`>fd_7HJ$hJvqcg$-bEWdj zQAGkdN*=~NT)XxT<3iN|H8iNG$gr{Hq`Si2Nek1uPJy5a_81_iKj^`@nNl3K_SI2T6pT$x zKizn`cjj|1;TNzI|!o(Hk*xQWo1>irv#Nz za7L+F#_HB*fVHPyemYU_LJ(1CiWr&xO{!r)Kb~rUNovLaLlz4lqzcfwxJvpCD7|AP z_B>$#MvsD9;S|`S7)}ZLydY@98jdZ})^{gC*Y^@T$Qi*TH@L!;cvE{pma0Jd0|+fJ zw<<^kAWfh=BNG^5{z8@DDlCCUZge1H#zf<`uOy%@aChCqC z-JC$n;0Q8ib9eNK5bS3Fkj-RV8EQp60F^~Z2%A^uVG3-REleJu)_`v|u{`VS6!NU$ zr~~aCW1^fFzf*h7*=S2@nL~( z%E!U6f!2=|vAXGiZAR!Y%-)7Cls56SeSVIF0r}Ovb+Lf!Dk~}$Y1r~$s8n;+4vtQr zcD+@&7570-Rtu0Gl-xFWK^n(A+ZV0)mRmpx5A>U;Q~|<08U7VxAQi7KVvZbU=HrRd2I*$7G@xe&dhMt=Wrj z`nqI?3OzV8kMV?!96AP{Io}?DQIv`;aET$bgm!nsd0D`AZAcT))vLDdV3iXBwVJ>G zFHrl1uMsw1O{uP{t22_U)pX=A0k3B-d{YA5)BH8E6Jn(pt0Urx&KEMj7==AS*L4F? z3Y-zBjO_V_W}{bl5gky3o`55dmZ+spBycAmzg7~l)R_+X3UPqOh)@#bTUBtrfNyKI z1wBGwlfygwhTq09g&Jfas)rb6TN({tld;Rq2ZNB%?}4)aAV&NJp&za|+DNht;*A+b z0hmpgyGtTgmwk&yN$RjKEUD(+4e}`t3pa*QQouC_N)B)i|9}elHu7{|J(L{WEJ;a8 zvn;}8LRcHog7ytY?PgioLSA`?!g~{Z>PHC=H~EyL690h%d{>4VS^IGDbs(s?{(Bz* zaEt)%$M;WdYH^JYd-ezxfvA5tJq1LhtZ%{3XKcK5^zGBjXNP@lc_6EFQ156CdW(6Q zqfaqV&IU~ta1%xjrK*g@DpB|p!%IXtBVLO5sbct?3WiqAN`CA@SvaCijzjc7og&gh zJA{otnrM_T4Fz?>FCAQL%p$}Xe^Fm-R_2oE-P%&pC2iw`R>Eipc|DMJGm9^a1h)gK z>Kd8D^zaw7+1Yo~(iY?e_s~*~Q5f#3Ph2`Qb2vHao*SNZP(|9m&88jb+Qxw&|B|`> zc!`FSiKS)aA>V($;|zTm@1R0d8;qJ8Vi33OY3}7IDXPQZbf{HJ_YU1aPx!lseYG%c zDhTesU2!rzK}&D%Qtw4XfP>)IZp|&}a`X097>$4A6x7|_Z6vvA`_zp1gohtmPXedS zTSj130Nx)S2Z(o{VCV*0bl+(~UqyWV)GLhFH%8d!=qToDd1HSpb^>I03lE{cqP!eK zaA6i=zL|rK^fCMmY|VeZT{SF-*HNMBEj!UKPcOH@+ks2esFt)j5UwISF9Mp!&z>n_ zv^Q8vipqcdIF=1R+tR&kslo2u3SjDkK$A(ws{5Ss&*$bkq8+0OMth%AO#%(!gV)e~ zZcp-khZ0~Lz(N5O?UAMg2@F8Y*ijk=U3WP*w|4w4x)CB9TGxF7c|VjZ+9ELYt(Nbh zEcR$^5Q5uEOn(JK2XV6q*EZ-L(A<~2{FV?5c!jq%19 zGMkR>k9=i>Fko?eLuiYjUDv@8fra8`t^N#yeH&hO5V_!tEEMX0R>U)taem_I;^J}u z+y{io6@+YX#Wmq0wU2y7;Qwdr+vs1lGgib&DMDq#@L2Uk!P?=!T-1CkO0+|LLd10^(d?-X1^NWZ6 z-Y;fa_G;3?7?aLTOfKz`Q0~hPS(K{c*^gLc41Vf*7)>1)WHZ)%xB;Uoleo2*rEUBK zXb(C%I$q+91J2Mnl>=hE_i%?X5(bX$$PN%@AVP1K26 z9ULRaX56d}_(KEf3JS{aNxu*}7Qa9U{orN6bAgd~1(gX#&(Uxre(s;7IrMN zbbD|vrqN;I?B_vB^yk5xT~|GHUewU?;^h7xQu?3SHQLW+Z)}ViEg4F5<;bxXh4Cn_ zlzp=)b=t4ZJY+G-I&TmVb|OkjE)`OIJ(Pe;4IKsn*z10kZ0u65apJ1OMM#X0)x%=B~eKbK1KIDYUg* zv~m)fO~(Ff21xyDZlh!x)LSmP4Uv$W`~p7h9kbck|GbMGr6?fV4OQEde-$0KgV2zn z^7#Mr7OU22^6ZURUflQh%fBW2ItdNR|8M^8|GZN&4RhJhnbSk7i~}jN&7{d^H@sTC z>3{dS_5WtQW?mPzF?OEN;Qv^V3q`g?JZmn`OA{9DneLaF1_|2siY!)d8vgIT&D`<- zxguIe%1rTx_BESmg_{M!=2>ux5pZuq{J004AnxuTkh8i6bq0o&rWUiT&dyGv!r{S! zAW)kO;ecX7&~+CIf+y$v}~Kd73oBA( z5q1a?Ov9F7&_l%RNd#64Y_1R>75)4V@a>>uyd!JvFvw|<+S_b+tYC8?5f@U%Ac1*{ z)D);s5_ViRcSooUeLqGB(+v$hp+pyhD+1YDBzk_L(~{8Q5#56bZjI|L1cqqp%TgQ0 z!27R{JWiBeyXFGZN@hM04i-1%X(ZO*B9gPg=ilTxEcGDNwbZgm^lw+=wd660Qv}H) zu(?GdU787}*$3M5a=I5=-X74Q5;k>X;|_!sN3k6#yr19Y6=ou;B6AuMhLDXveVR=K ziKg4I2^`y1oqaVTmv^J1LEtz!R1?mT zPiUT-*XSokOV0HI-2VyOLI4?Bjj*1WK^+9QQ^+$2slaTa)3MnF>CFm|IRd>R3?9`f zVj5xE!vDca&YU@eP3pPyzs}GV1%E5p z&&UXcS#47ip)i+Dbe%Lm`E3gHuw*8S4X%#yo;EZ3Cw8Ih{{6%$MTG$eOEws@V&8+E6A7?Y zyTrNhS0vk4q=CSZF*PmCyTS_B9mL>4IwDW*9P#Kh+`YRLFR!GmOh|bHt{F)dBMg{T zPn-){TT%>V_L2ML_JVjpz^atpym@Y`n8G>~buhbZ3<+>{XRr%8JJavF$~C&%*q9le zg~+HOyh6%OK(-W_#7@l2d+jS!gz4SN`NhRwkcB}M%{&_4=+#CR(3(QYh+8 z`)qWjCmX_iGm#Kye&eM&{14s@a{wx!ZH{GbStYS7631#*;xL~U=M0l}DDsiB34@P~ zV_nrr_<6vh%dIEMi8)Ve6*}P_-24Tv%L6h@bhl(;ppj(a z_LT@uEUHvZB@@lI@PxIhW?#P^y>Q#gcw~*F_=_AGITc71nYLxJ3TmeZyob;NxhP_O z){bw`Lwg`dNX~n-Z-5lp1h&lDgy54*)vav%0l?uus`D^WXKLEK_lEQ%@1m@}j&1gbw>r_n<6kJorqwpZDvq#uf!`o*I zZ$ws9$WQGo_Xx<}aU(|fpz@~)sTidD4!j032K)AHHIdw1Be1yS<$|Cek@>OJ@jw+-fD<425U?g(~}I5$It2VOgGYzsmYc0x4_w}Y9` zZePpK|G(NPM0NkbU?CP(R8n$BtZ@h!se@H|t1^%qTl_NM&o5$=0cKLb@M~zopP(=E z1z&hDl}G*P=R{L$XD6MO_vozef<{5UxD>fuLW$p36$HT{Q@hcqZ)|CK95{j8r_Lt) zC}b0mCUHKX?e~C_9BG^^T~QJ}qI4`v!P`E5x?>$_*dU(=lk*0oM9R%b$BHH`L)jt8dTJ~ugNm^b1lO5`9JyU3>CPVV<*Rt|-ANb3F>4#0%^Yiu)1;Fy|iSzVCT3f;8k z&U;j|l8AGGj0PbKo=4%aadOi_mg&VLGRZKzm54$|p)00gr1<&x@Iy#;IdKSi6;>h| zj3)JZQUQk5BVr9T0LeIxdck_wRTT84I%BuylGqPyX&-U=gm163>pe<5ivWLwNGRg6 z&we;gsYZFJQl>;eT`Pxj9nN$+_t4X;%5Ypz9u~Y zmwrjU5#p)<6gSL>5jPU+df5(^CjCF1VNq zl#SV{4_Q#cpb$RyCCD_OPBrJ&HmC_erw_`KziW$*#qMV#`EohKXSV35I-%4jmqL)P@s`yG90u!xT8li(PB6AIg~x0i4?tyE5bXKHuki9KYlE9p9fno@d%V_vdqcuIoJC=lgu0=h;~` zoK*L+@9z-MlLHx3e~ib=)!c7&W0^}t{;yGucELMYj!uU&sU89Qul$IcE+5Q{XVFkY z{M(lk7=6|t3WKa4=kl688Zu!(TxwWNwP<2Ic7sa@Ds5x zP#h&#@vk4PKugc8=LGm2dziYZ z{{G5?er-FG{DH{Z#+TQog|)Y|tzVY>AAK&ZL)`L$o7l{xD{QfvkBP|Gmb2qtT+fh8 zXvKS%29+8l{PgH->UV8Td@hk47;5n>g)GH%Xy2ClE~2w@{S$Al#br}#e-&YiP~wnm z^d&n%wpy_Ts+&xG}+9eo}4ht9I5+qIO_TCE%2s`1*1>BsjRvfur9`?V(O z!(7K?{-NrbncZ+^V&aM;Q?eVU7zHj)%!FKRyKk9}&SD)O{XhSl^7>ubT$y3gylSnN zFBAU!y&?LgNK^BVlY(qr=X`3tkbJ>~M!{x(N zPT8D}@dOY@({k;zpS>?mVv-N6XNXH$Q&TF17_X2N#%c4+IhSz zXJE@EXNwMA%Ck*cSDhYcHqBQS8|<&!VwGY_i&h`!{IasB95tJV&qh~ua(io7|Hq}{ zpXSAWHVKyB==+yrLj)%9bToD(01G%y%~VIN3I^ z?&tO{L2nDVzI>por1Kxq=BD~5^I|nL;}sX2_?uL(##vQ;sN&1L7t5So7RCJgpI`6^ z@)f_}tlFslPjn)~^i)~vYLnWZ_r9QGYVUj^CTCgr*L$pc?E2gC zd0ViowEpLLu`!X}zF)7g_u@Z3Bl@wk32*U}rM}9N`j6E!{phQw@=N{Kip6g!e=h46 zy@SPVn|Cp=QViue*D=z$Iz~gQei>r&%C^L4w5|L4SE*i`^^x9slmE8Vz9x|~d~L_q zEp30jn9rtN%<3;_I{9f?R7}eyXX-yQrz}D3^w*`&VHMOCKUDKROUMOXeB0F^Bu6)j z5!)GEY4Z7*5hxL>S2ICK_+r|@o-t4Tl9rzS5g*-e+O4bngT4{mgt4(=Z4>S7A}*a= z$x~GJy-V$%$Ry(LML$>OuVup{hy0#-kddhGNN+|sIXarr%$ZK7_nEnxy`Mg!VmJyU zM}(brgc#sQ<5KX_i@AzL2=}Sqf@emEQ7h03-MY0{l@`a0Vd21mHk(3dzrf(M4N*}u z?jCw0N-AMW4sdBE7-zptC0Y2O=F0=J(=A)HfGX(Jrp-b0OJX*2oJDq8T1CH;9~INe z%qn84Pls4LtEvwDkjl_B5_KiFZl!%Wg!ly`9#DQ4UlB?lT3=t^S9cD+rV*}ie>Zk} zi&3dHB(VeYyZJK{Nzh32*abBKoeMz*%g#Q;3JzhGC`l1|Wr)BI>Y}H7XQVlG$2 zJWv-_t!(ankxOU1nmq@1HhzrW7PrZ!Q7ug~;!YOj@}qj{^p)C8{o;WrLlu2SR(4(6 zmYg5-Ols(+9;2A^)c3{Wual;yYWP3}JX z8U^DbjS=1ZG0x@QzDa`#{NzK&PMyxvT(7P7KwBnG?ycX*wM+$kFtl1o9A&zLi`Vn5 z!D7NQiQt_F$YIPD^|c^v0?7l(p-fyPZz{C_NA@r97){%0(MP(wp&_&F?m4UXTQ@3_mEmzrzuIyOeId~G7pPBjTT~V1Z zbavB*goNE|N%{@BMy^tEZ4TI#JoB5(Qp=8HC|i8|xKX2M^XaL${i~*tF$+vvDxYmx zB4Qypu$Y7dQ2(E)DJgyff!jQhPgMADCMrCLy$%f32t}Or(g4i_^N2+J$-jO@1FA)|yH& znv+esbhc_pS%Qeam`0SjTb7V85YWJQLXiYyE)GfXN??AgCd;daGChYudJ9_>yu+uZ zoI7V{9}`p3+tglHUw>*1w+Ph~^wPo2tq{TKRQak_qAq(j+DBoJJrb|qxPdx2F*(`x zFFFW_sh_?K8sq&PN+GJqk1@Syp4$3o=t~*1&_h`I6}CwqK71h6)P*62%!6j?1mgpr zJF+Ax+#)l}rA(<~Ec<4}aK$bG0Rh8@Z)Dv0n7UV5zatxZEm-iG=qY2jEG{lH4U$DF z;(+VxWilC1^QUW!PFRGF?c&9Yvwy$w5_0z5-k%+UgM-bqJV#TPziGpkEnDK_jndij zV#M?sK+JS27^-4VzMM2^TMBW%VsiS@_&R^G#89)zmrBTU0>#ALk75T88Pe0(xD!%~ zS+lZB(s%8uCWH3q@nhye>ZCKMD55>$3}iK4BS#+MOUBk`FrSUQmvhD44$D$X=fIgDF0mSxWbn&^91T#wC~ovcke>pC=JE)L~J60 zeY82u;s{~7W-=)Q5!WL5hCN~MhgVM{G9HL$;VB-1%(s|KitrLbTEXk|rD+N91ZsFSq)S+okrt%Ccv(1KlU z#aZMWzO6fT3d`+zP)J?;b+yz7LgHbw!anAD3$B+Z9L?#B6dQzTp~eFTde>gL79x26 zL@ET|tHm(lCd-TS1yj!oAn-vPv+vk3D^t_ptBE#3Ma|2&?lNxNeZs#=qV4{8cLWHT zyLT596x1N%w4z}K+>`=gI_kxAoA~yMv!2Q_{RQ9CHELsZ`s3vGa_XQ(a#mP+db)5R zLf?*kz%=8_1I>EKZ?3W?+8M;7m$Phq<+7UoZ`?l3iw))sDv)P~-gS$jc#;>r{|(xa z{}|v|hkk0mwsUSCIitBSwRM00-(PR!hR-IZU*TKZNCL4iy_U*f(bl(rFTO51iK1zt zhlj_+i4)DVZj4xErwum9Kb(@Hlhm~i>kU1p+52Pb-W?LbH8A^IV*Ypx94Iq@XNM^LEK(Ka^oPg zAx<)aB^&PUtRR@g)Fd&UDJQ47R~;Pfs}~7`m6~)|3Gw#hk3Tv&4IMhdGT%J=Jq^f% z$e78Nd!4a$~N36|zl7#A4pt4j=xAUzGKk zQ&|d7w+#@279r*Y*weM@Hh4%XG&X;{yrUb(-r+4=V_F~nO@Q&-DC6C`cDYEU&2{Qy zC}&?zY2)Mr2SORdD-$yirAWQgGcz{|mC(9%i3gZ74cIlFLNbuc%>ta!v?OZ7hMlzV zLRlXL+ukW=6r*_&7GKX76UO-N>y^(RoIKfiZf&&KTQA3la8Ix1a&GS6!Gi^qp=#wJ zLXf+Rd=54mIIxBwL=<_zJtnq3fBrl~nhe~&JJiI~^e9;&{*4@&mPVft$IE$nd6-j6 zrl4*F3LWMDsY&@xR{w7B_(e6@!2P#xgJzw3cTBX_e`huHrDmHpB#fk+t>y*+?F=+! zKE9-0#*#Y|23*kg-1&%M{0voC3P)l`+@V8?{^TaO-`B{X$$upD@g;zy#vpUBCvD|hVJ0l|ZYh^vXMm62^vwe-fLlz zs%=ih#C%55RwU%;45*#|=Fxe>5xb}}7#|uMN+<#P#j2lqSjp#y2y<=ktMQ7*YMT{$ z*SzkkV6E||tXknt$2v_OW8_;j!}taCK$1R2-Z zpi7sBu(cwAG=RIsqeh9km#6BhgiCi=%jya--}~p+nVSwBL6)VXiH{=)vUokOO(Y8l z{=7+`(^awP+O;N4nkcqwkE|zkgqs{reVK8opn%+R(fe#&9i0UOoxF!G+E6+4pU3-e zgjXF^QtM63ef3PC>0lI8YAsjqOL9u1j)WWevxz``IxUx+u7e7D4L(lth19IQgu+Dq z`;V|ud}RIa_ud}XtAobBM}18dQ2lQl+<%1%zoO1>e}8B17=j9CncC^n+LdY)BL}X3 zV^F^VvflvH&T?L{ZG7d)6f!{st9Vcx`NxkRfzF>&alEr%mR!GkH4gxnasX+>(&P#so05lG!QKFyCGdpDn>+Y*tBVTC{be|=l&$) z$=MR?9d&y43;-_T;u!S3L5{Icg(>I_R;PsHI~TzhqHik?i#)Zp{7HMs-@15CMlao@c<0fh zM}iH6Jaw;uTe^*1H+Y5y1L^hjL4$rt!SN3yGfh|v20Sf_n8}g+3Dk2FZW`f@R(=`pSAp|JE5z*w-bn;sHr-sX^be?xAlGpW)0s-Lku8x42oh=ZmXv*8JT|58g zZQAf(H@|?dje7Q6NT1$aho82W`u;&o?nIDZ%EY!5Peir?n&xqf@Ro3CupIJJQ(EdP zP%o7KvIF9>c$U8X`;Ya0A`t0@$jD$sE9sdyus?pkIRk4W=7c@nJXPl<;1n&0Y|vV* z7?~{j0CE91uDdJ^bZ5Q3zmOysI=ajqJ7i^Z$6?lBBa%rKZ6|<_@JC99b<=%P zUT#@l?7~lVHg4E}URqC2Z~B81{adVZ!~;Z?M*iU+8b!yP=wBP6b{1?KuN?-d!SDPm%DAO~H!{FjWhwCFxl z+`l0QE%SAVR}w_NMbmG++m4 ziRr0B;kF^OKaC)IrRVW}9IxD~AVpPd5J}zP@(7H%&B24EPTnnxjr}ZIOPZU1qUGjea+C z^TX^9Y#)&Fxd;bzp(ImNQwLuzD2OTs`6Hs=a5Tah!j2xlG0IU z-_IA-vpvR*gHrC64%SonIC+++7YwU`rUl0Q1m&c^XONTFutC#QXl`}P%f;N!Y!7em z8NMjjhY=B>6B!rwam zTSLSdbHQg~2)2m#Ns-FQ4oJ2z&J?ap@Y?pY>y#2W-UAMyePX8O2H0i$nA-qFj$RS9 zz<^AH%jUDmKZ)W)@wh?~i-0NpJn=XI(kLk(l~YjAM*h@|`>&RHlL%a{pG7l?&t+o3 z9Cd)y)?vapU5XNq2`Z4U#d%Gcmay$pkYZFeK?B z=J?6Ya#P`T}EEt>#-%@0r>S^kwhzDp(K|n(bf2nA(ZFkS!z4s<2T6>QURe&Jt6$;w8K4HG} zo3H2B(aMVbtnuy7m)7p^S_JzoLN)(t;}tOIT>GMd(dsMzJ)-;;VK$Nz=%?zF;juBG z?0o013H3_~U{#-B|Cg`-0m9q*C^F;AYk8Uf^!3P}gJh;3pX$VDG_4Oy>MV);9ZDvy z`{wJlIXWcY{}u=ARm1^xA9?FBI)^PH+f}FZ*z%Ol*Oy7LxMdp8_p}&0)Z8`e? zDy|?M$|b+*FaGsi|1-L{5gl9Re8OC+hq3stAN1B^#1Wr^{uV>o%RNlJut6(WZ?fLW zSg8ppf)9&5GE+a>b zXw#`v0xah4q$5X4^00zu9`2D}$$DyCglFrJH;o1X;4bgu^WGEbV0}d`h+0cMbkNQn zMgjM+go2wO^uZjcYnfHjR^TsY+@`DrZWV0l_ge5OpiVe{*hCk+34e_sc(CHLf<9RW zqmFSwW8jy%YnPb58%7wiW%Fj7FqvUo-(MHu)`4G$F>{M=K#c?v$G^UFaHt!;mT+4% zz!1q9Tuz|bPEV1CgOomo7Sew&ep;jgAOvEO86Yc_=c*;nvK*u)BJJ)cJpCMOXKXNX z)(-uqk0IU8eDoj5QNc=%#kC50L3FIh9*Kk%Du$f1P=g4e6sciKU!t+b$k4^aVDLPX zeg}?NjoT2Vn%q}#{#c}h5@QM@R>fW-gGt{Pg$2tt;d!VRkFVS&`bY2Ij+s za0kv(;L~w9AFG1MJ`_-*5S^3(jdJ9qEig}M@XWX&e&=TYSKMaoG_BQdAG-`&8Ue0{gqt&Q*> zL|2}nukU}sH%cu~zQccCzd;yEDW$EH@H@h5pASU~IhQU8QBCm;D7sr*zvt)!$ABr~ zQ&T{uWIG)#NI{T9zU~A7|1eWS*Vxr^r~dgvxX+ToUH`K6AIbl&kMIwj9y zD1R>Xp!$<0uT|yV^YYO1Ma3*c=8p=f1a{NY^9DRBIj!GgJyyflu`8v|Z|_enpX*P2 zK0t;HWEA1g#*Nv4whI>qN!bGa`o_k_UAt~23fi;BRsLC8RbQQ!-z1`887UdqR=VB4 zrB?#rd8ixkK%}u=Q+9<`$R1e$IC_Np^y#}S5Fi+*6PfMUf!=}?)mY3$AU`OP5(Eq# zY77c8GP+R>5(>C`Xm5JDw8QPAvvmSe=#&P6j|$;Gxu7c zALy{kP7%TOJC#?}GpOD%B*pP`uZawh&Wrp4DoAdg*pjlM1rPy?SZod&-BfH0G_Rdp zQa%^{gh~PoD~n zD<~?Slkw>M>Jgbe`4^t5?HP zYzV<1JE22>S#Y&Pq~pfv=GBXKFVrC-=%P3dthwEjEEAU1C_OQ;vsfIYRYgTbhTXfP z^o_*e?8xNsLqs^WuM>`%RhXL!UmSznQu*bcv+6}P=P+ofWatAl0FE3lw<8lB!Z9R5 z?({BbHS3I;KH>fh=n0{BqP5vpPc&!;LSK4GspSsImp+GJA!{Q#WI=!%w^+5zwoCR~ zf<*`I^$gfLy|l3cX)E|daSW=Ufsj%OIlHKW_i!=CmD%@11VHHBZj~K=*5TK-Noit; z3b|H(h%UTL{?YN=1O5g<)7o~XgWMhl&LO2hB1B{uM(iBfnGG&zBL2??ux8g~K%i)=Kr-=4ZXq^3LQb*IDbh${X!*GfIXDAy*mJow}mf*ET zixzrAHkZXJ*yqOPwW0y&=n@O8043?dvWhl(RF%Bok1?ev-$c$zC`2$^9-^2~)C7td zRTPp?4rl_ckd+ley9szPZ0%uEKnRA{qZA{YA4dy0QQLz5`hfV*(FfL}P zCs7oD31K0L@aZL45Y_DNQlT;=`DY=Eiu#jxAhgjW8z2kwa&s-ME}N&WrEupdf}v3$ zHHcuObtG{6lME;BRR~W$ht;Ce2?K+If?U>KyXm2^ZMVMQhYaM0GJLIB{n?P;m}KI5 zIe%Io)?AR9fS#}9sVUV-XrD$aB%)u_4RuyhafQ7Xb7H~g;<(jbQl3zOg8sKZ|0a_8 zEz%r6Ea;$r1D(GG`2XqmJJ!EwMZ74P7+njYqwA6*_2I^gnctx7kqXgVy~O2T!jEt6 z=AR3=_d=P@pF$5!XZ0*1dPaK1(wONbf!+HY>vD#)d0>~{)p zwKm$mSbO4jgUFchFx{c58m0*D^)jaHj4D(3-o+;A+xOo0k&~P~cTC?I>D_nDdqhE} zTPmkm?KqtN@qEE1i&|fE^0ZI(n(x1VQnLD zzK{ob)4IpVt2B#wXqnHaljTN;_CS<{ke`4DnOIqRn!nx-pvQcrlDI~R_&cp|B&A2E z5DWkf#tj;D0ae#JnM_;qVY(OjJ5Hu(vd1Kq{ao-mGE)kuP5ssHQc34JHNpS{l*dW{p3Ls9ZK4nJiIMz_e*EP!~!{Es;mfDy*5( z%q20ohi3Jgu!NY&jec49z-CwCp~QVUn#0gJqwNtRL%fG{!gmNh68a;OKZL?$d4_2 z7D6kc&f3qjtgNhdjXA6xnbV_34{N*PDGgOr?jD=#k>q;G$3)|c{>nt7i~0HLc6M>G zomJJ<)%lY$S{Fn{wp_W=!kikU7cZJ7*Jw4bt!T2d8{}M9`o>hmmUV5t?0nAI)z-!^ zn5g&v&9Beu!mI4Iy4{pN>$Cvfy1@*$U-MLm@u6jD5YgqXrvGKHt!jhl5d|9Z%;Oyyzb^bdN8DvfFpIA6L)007K z%?RNLwOgnt+1Z_Zf`o-6(UtssvRT=C_8_qO6X)~d`SXZ2@cKuBo;ZQsJu}|FdD9Ml zefMtTan`9t_m0gq?b-7sz5iHwYRYJT8GZP$`8exLPpE0ID*?g0`SY8#GdcI*K_@=O zUX}vLg&)i&&da!tP*t?XP*PHAvRv6X5HLp11#^UzNv^ay>)NMJ@x6N;jOp_#BWXxc zJ-HVAHv65#80m*&*60d08jgT(&-Th>8(vNN3D&@->gcM(U606x2pYl0MyX`v=+UBE z07_NR_~kl-v5}jyZbeu2AYqn7tWlSh}@r_?%liB#(@0UJxEQ_*eXpfE4nUem_s1e2_Mj2q>xE)cB&Voi_Luuaa1WrH_jn8yL6;g%YIBcTJkW zor5MQ7NL_Ti>=|6Nf3VAB2L{nl-G(L)~`?d!Gm+*mUxU8FJ7Rw;t!)RdU|@@H#%h% z`t@O~nxl(Lqtf#7o7^nwIU-1+B$VZiPhsl|^T~dkJ$n`i?(FPLMDgO~%T88v)*%Jq z6ZAn_=D`ejvpsetgqZX^A<)H^)7&C!&z?(I?Sg^_G?ZpFCr(UuPgiNaelglHursGs ziPmyM$)_~5ITTj2$k+ENqy~X@(5I>?VPRoe`{Ah4(hMZ=@$npksR04U=f3l0^Ai;J zX@7+j?p2pXix$n9GlvX_v)*@VYBPg_(MO%&&`>^{O2iJQ^Jhh2gO@LeYI)%naruRX zy$uW;jRYw_VD4KVa5A6&7Xmu#D$A9L`LBsW*lk7r*`hng!5IlBPYw*nL!z;UvvQzYN)0NS?WzdTwK!?U>`#4Y?G z{SPSTR1Og7AS8ei5!e9QaT+?(Pn3O@U)d6skWfQVyS8obGlhlC=drDYGiJrcj2R>T zj0wcKKKqFDD=I3G*PzKP=-s67 zkdyTM2|7Gf5Ntp~xTO|--)VqVmo7=XZ3)q-=gyHciIsT0L!x_i>CvNU06QBActe{H zWcjtbb{*b*qf4qKf&+%ju@R6+AyzjhGgbk+vXHVHb^y$PDAjUwR1N3exlsf~9Z+mR z(NGxOS=(d}u$ux18x1l3ZqA#DXgVc4EVy3Y@}~d1Eb=7S@U2%6nZXn(EnBhT;<B<$+(Ur3>kVuFI=vOea0jNmUMMb5$r&EzY+q-mE1G)^eFSF*nc|@045|R&* zmoDjSZ45EPB3Z;uCt1wr5hbd)4jYz5SPHdCrgAF%l+p5b{?t=D?cHEUM+0P->B9;O z3-L2ogF{2{CG)v9TaTEFOwT0wdGAlt`V(A$&nkcVlm;u0MzCW_(Um3}OuuuWr;d)w zJTzehd{8DwDRtrip}?5r@85)G(Om`%=y(c44vpeBeY(1}#xewRtjr{g?%~6SNiiWS zSbiUoAlZ8f%*O;?(N#{W=ufsAT^)UbaYe?X4P>Ym_g|nq=h?!g2H=oyiqdyI7MQ}@L#Uk+I<;zZu8#k87 zbk1M6@MFJz6u0#3-u*KEaURRh#wWdtlodL8_Oxj|s>sRHYC(NG>joBE9&ejO=hXXH z*{#XRF0p3o>E=imql)tvFX9tMjT&|S+&Nn1wVa1@pnD9$A)XZlQQgaroH~8_fZ4CK z!Z|c@3uk6&DMkY#vT+$YH0(oV(r>>t!w~dGEDBFhc6eb2T4Nb8y;iceppo z?3jkwV4co03IHWbmM*33#nRB7J9i4wmNTSyf`*M7FI|}yhlqq1uSEB)odywp4jRi? zwMDQk{@F)%?6`dTv=X?*&B>`rIJSSzym`rSrKP2N_Uxf-Lu|x8eWGJxl+0$o8bgbK zn3&sCv|lYGum1S)V>FLwRYdO1IY3l&F6HJvH`Q2iZF~WL$s#;>9$cNrEt6sr1DQEe(zEwSX!V?yh6T#Ky<( zy6$i9wlgj5(ELA*!qLc#N^(uydiAR7Huu|i?=Ij4frB)fXTPg@=FzNKGpUS6A8)Op zBB3vihKjKN;5;SyC-ScJ^!3|R&|S9iDf$vi*tKVG-fYtW%$*AnYHIq9)CHJOsS+t1 zthXd7DXCX4|KhZY3cqYG6oD^ay;4v1)?H$87z@bI(*~-ls^LoB-risykPMrKY8gniko^vRP+GiG?ZyUR9iYqpTTdO4T zf_s6hCj0xB5KzX&iCGTqtV2V83@6CtuTEQATc4`MlH8Sp!$sURRze9Vx@58VojZ3Z zS%AkgCUP?9+_{tr2?=d$0gTV^84^&@*fC=gXNwI)bJv1`W{q&|`l?F?Z9NS5rwKl* zfJVDnA}~~d-k9iU@_UA?ESQ5$=CqCK=-r18kJ4BMU^pF~kdTm`J_WZL8u~{<&F9IA zolPVpp`jQocdzl8wCH(9e|U}N?Ub^;?v<1@Id$dA_l@$dT%nW@q=z$49O32F{W(Bc#Bq}ysaG4S^f13FwrlhD4#j`Erv+niQg@tGIf4 zHnNtH0&601+Qx+Av*Fpar=jQ11yCo+akg9l&9 z%L77nQB@A7FoC0%cjk<8BYJKbL`cXZNq8cwg`YITowl8MH*(Y{CEWd@PgUI{*hK`OgC;`xSN#VH84HI3+k_IuK*;2_Yq!!l9Pu>rD_s4O`z6L8=)O8leN(J(pnL} zS6LA--y?}ek0g>r?&-)FAESy<)=qYL0_MQ%(s5Zr6RA#wYFHCE8;x1rwdE~F(z<4P z{zWA=O(=A`c5M(2P6H;8xBQO3)-^Dwz*&&*!nJau28L8zrEm%LI@@0$o#gzVcsW$Y zUoCnT(yOzyMb%DvJKOoF4C~&iN=LdxNh(XkOEy%jO@&mZqpHl2Q=5lT<@hZhV>BZm zfMC89S>am~)cQNvset8Ks_8*h={NW~WL@+Ru~e zoDO$MT^hf9c|*zJG1IXwSFiFA@eVQ_33td8hH!pV6=Kn$X+?-d#i-GvmsT8Hndp*F zCOX_RBNH<2-MgP0&_x5+8=toqkWM*MuTX6n-Lu-C> z@Zi;)9S2Ad2?WmZU2p(96>uln`z&o0T${jLL?1@lsrFuTg*Cr*4+Q4x($blkYL<=wh>S59Jf28?Fp z5`BGru}O<_=FXpQs2M?AiKvj)=}B8b8+5Rf5N9-ko+1hbL!sTx&CieTH9^^82Z^*@ zyF?`2_&B06iEGAnyvX$F(<=-tEq@4SWicNTpd}^=0UK$*wu0X2)=jDO@#C(l$^=t% zE0_V>fn|oNLGlJm(P!MLix(aABAe*C{wsY0LNi|QiX1{?ywx^`zB@FD;VUn*eSey0c zc9yQZq{>dAn9A43CuKV=sYLG5!=tOJEf}_n&M^{_j6eyZl%%cfE#(T5?yFaSC&BOY z_oA`~+OB6$%9xd3QjMQ@=uq2Ji1MuMd`f|Js2~=>hhT3|0zQ8F^p`e*S>C@tn2-mg zHD`{Q1r*gLmsF4RVIuw_$`?vt+KJ#Su41yXwif!~o*SYKRbqW5;G=~L761Xz1CB2h ztuWHlo2zs&Fi<5b!c_pQEG;a^z2fwewz8v0nM)`-z507m>Tgcl{HIMj2JOHNUM+;^ zQUdb-_34wchDsNb*b3e;Ir$7R)=Plh)3P#&s+wAJ6(!NU1jmEZWo@8n3JWP^4Y7!G z+V=L(KN}a$Xl@fnwfc2DX~zKzh)B=lUf35|>vf*NVF^5V8-1p5+|(oX6FSZY)i_wV2L z)9=DBH6ri=L@(ux(!-ein%KzP_jqnP^#6S|9^f6ISg0? z#byRe0_b#h&cib`vo!k3@>=&nhK;9PBFe*1`HigKP|4Gmk zg#-qM$FE+U3(xcO#EEMuf~j*}9vRo!QRFWNZGk0zeQWR5J$nLySut?qu&Ja~ zhzLJHS6zM*R1rrlpV)e2(w~)iVGGleE061sH-}4>J+!QbqI7D=oWtuNku-l zY5#5R?HE$c2Zb?WgoZ1zma~mYXat0l&Ux}yK$9ygn|I|}ghFJ=e}qG3msOzBMH=wE zwWFh>pPxYq;_C`xN`Ngp%_HM@E!YXs13~1x z0|P7h2GKoQ4V6-=?C42pTmq*kijzgHIYqq#c)Kh(9WYtZK@JXW+O#2)uzT!LnAS$r zv8;}WkZ57rh>Yeau@O0q!r=2)w59lT@WG;^xGaze1qB^W5p1G&N-ugw(JNW=H9s!t zjH3HTUEOW$5yE#A>@Q$MsPuu=Aa*y7%B3Yk0}ybQTGOVVU)NN>7BZcZ>5!&0x8P}m2%Xl8* zf210(3V+%{Fcij|2+j`&Q4iT*T4pA`>$Oe)ECgi__%Y(}LKk+j*canZFM-SEQIpr$ zCXIwk#DuL}yUA$m4$lmJWu8~F0DLm~`SRDVn`vt|AbjMANppW#AP1v#GNAh#LQP@O#%b>$ndV1Tbq=jkRJI~KFsO%C#3o!2DirE@A zW0$O1Lk;`_Q|eVvv584IefyG)5qtx0;NZS}%FPQqIchAU93g*m-@!?)r46YX0zFX@ zK<|<0s3>4#`NN03p{D4!PAki^r>JNU+#7QNzaSr>4e8zk2mW~XZoU{4webCL8;z^9 zbG*ZEk!+gB8N|GR0S+HI!c4vl1fg%Ls#Ka6zGJ;`agS$f*f$U0B`GSRf9qiBi9G7M zKQK>p4e8hsP^R91{fYJncDOORom4Vj;Ii=WaGvZP_Uad677U1(q=}gT0s{Sp4wd(d ziZw5~f4?KSpw6mi01D_;h|)t~EeO62k9qazQH%ENMNI(q?~FC^6ScHkqNC@4gNd5l zG>;!YUi16O>ffo0rOKt(9#~9vnBeLA_oo#w9Q;h0m%Mv=<#)_NQvfi!YT2FqP4#(qJGa=KKrCK$HX7uh77d57#j~mtpGPFG^xihC z`(zRONM-c9URiCgotC>k#|P(Q#V=R4N;Y^jyQPYaroBe9v}$ehN`e}_{}sQUY(!bo zXXZ{y2CZrTc2)JKi?`zC2WX@)VNZsJVI&_V;4E z9iU-qpUt!&hA9YvHwOFDIr9n4v}r@oB`TM6?*p2va7FAS!CZ|{Y&UF(Wh06cL#CLs zNQ(q(k;@P3A&>FLMh)7^P83cB&VYmpEKF0Pr&pp9cEt(?@Rae;Bq-S5=jUNwxEPS0 zERUOkCMcOFnLCddzOlL%L{B66%`aBmyj%MEgDO5mBz*hQaf1*JZZGRFNw+iWe52#EWLB8P9&ua4}5|vP?oz1W>Gp;^Uh)UtCVFx;Z09wk^5z(2$4i_Ho)C(&>%e>WFp8-*)x^Oum&V6Y%F2^ zmvp{$=Z-un8U1{*I3?9B70cLDykfY8#Mw9@Y8K5cEg#Q?1&MbsibCfvKR&-M6(@>4 zDbtB_;rE(@Hqg!rtU{!78zV<$to*gdZBqQ^`P|&!$$QZw=Ln59*jQ+7`m@C#ip0aa zmFtKLr)6lU*yu%4O26!vUz9&kc}7+s*)s!B9`-b^9sW@M%le96o=T2n@woBhOQBVO z0)O&0Mf(b5h4K^QqIgR;MQ0D87d{#qfJ%t;#^!{CPU4Bv_BWTW)GX)_dM|P}tMJRB zBUxZ6Q-T#2u~)nlF%404{6k9ZK=m&>1|_@V2_qCwcm@KlKom2JPP4piuA>F-&slR% z&)U0};H=R!I;(YW)zm<<39ZI#H2!femTcYn7}P2eDT%H`MdV~%wiZhKrcDFo-#RkO zP_o>1@mg{pqC=HrPjqkNkzC9mA4Y)& p=l>ToDJUAE(w{2%SL_@Dp) literal 0 HcmV?d00001 From 8a1df3960910333b0d8b9df08fa82f1ad4dce509 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Thu, 16 May 2024 10:07:10 +0200 Subject: [PATCH 49/60] new miniapp --- Blober.jl | 480 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 Blober.jl diff --git a/Blober.jl b/Blober.jl new file mode 100644 index 00000000..be06792f --- /dev/null +++ b/Blober.jl @@ -0,0 +1,480 @@ +using JustRelax, JustRelax.JustRelax3D, JustRelax.DataIO +import JustRelax.@cell +const backend_JR = CPUBackend + +using ParallelStencil, ParallelStencil.FiniteDifferences3D +@init_parallel_stencil(Threads, Float64, 3) #or (CUDA, Float64, 2) or (AMDGPU, Float64, 2) + +using JustPIC +using JustPIC._3D +# Threads is the default backend, +# to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, +# and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. +const backend = JustPIC.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend + +using Printf, Statistics, LinearAlgebra, GeoParams, CellArrays +using StaticArrays +using ImplicitGlobalGrid +using MPI: MPI +using WriteVTK +# using GLMakie + +## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- + +import ParallelStencil.INDICES +const idx_j = INDICES[3] +macro all_j(A) + return esc(:($A[$idx_j])) +end + +@parallel function init_P!(P, ρg, z, sticky_air) + @all(P) = @all(ρg) + # @all(P) = abs(@all(ρg) * (@all_j(z) + sticky_air)) * <((@all_j(z) + sticky_air), 0.0) + return nothing +end + +function init_phases!(phases, particles, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, sticky_air,top, bottom) + ni = size(phases) + + @parallel_indices (I...) function init_phases!( + phases, px, py, pz, index, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, sticky_air, top, bottom + ) + @inbounds for ip in JustRelax.JustRelax.cellaxes(phases) + # quick escape + JustRelax.@cell(index[ip, I...]) == 0 && continue + + x = JustRelax.@cell px[ip, I...] + y = JustRelax.@cell py[ip, I...] + z = -(JustRelax.@cell pz[ip, I...]) - sticky_air + if top ≤ z ≤ bottom + @cell phases[ip, I...] = 1.0 # crust + end + + # thermal anomaly - circular + if ((x - xc_anomaly)^2 + (y - yc_anomaly)^2 + (z + zc_anomaly)^2 ≤ r_anomaly^2) + JustRelax.@cell phases[ip, I...] = 2.0 + end + + if z < top + JustRelax.@cell phases[ip, I...] = 3.0 + end + end + return nothing + end + + @parallel (@idx ni) init_phases!( + phases, + particles.coords..., + particles.index, + xc_anomaly, + yc_anomaly, + zc_anomaly, + r_anomaly, + sticky_air, + top, + bottom, + ) +end + +# Initial thermal profile +@parallel_indices (i, j, k) function init_T!(T, y, sticky_air, top, bottom, dTdz, offset) + I = i, j, k + depth = -y[k] - sticky_air + + if depth < top + T[I...] = offset + + else#if top ≤ (depth) < bottom + dTdZ = dTdz + offset = offset + T[I...] = (depth) * dTdZ + offset + end + + return nothing +end + + +function circular_perturbation!(T, δT, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, xvi, sticky_air) + + @parallel_indices (i, j, k) function _circular_perturbation!( + T, δT, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, x, y, z, sticky_air + ) + depth = -z[k] - sticky_air + @inbounds if ((x[i] - xc_anomaly)^2 + (y[j] - yc_anomaly)^2 + (depth + zc_anomaly)^2 ≤ r_anomaly^2) + # T[i, j, k] *= δT / 100 + 1 + T[i, j, k] += δT + end + return nothing + end + + ni = size(T) + + @parallel (@idx ni) _circular_perturbation!( + T, δT, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, xvi..., sticky_air + ) +end + +function init_rheology(CharDim; is_compressible = false) + # plasticity setup + G0 = 6.0e11Pa # elastic shear modulus + G_magma = 6.0e11Pa # elastic shear modulus perturbation + + if is_compressible == true + el = SetConstantElasticity(; G=G0, ν=0.25) # elastic spring + el_magma = SetConstantElasticity(; G=G_magma, ν=0.25)# elastic spring + else + el = SetConstantElasticity(; G=G0, ν=0.5) # elastic spring + el_magma = SetConstantElasticity(; G=G_magma, ν=0.5) # elastic spring + end + β_rock = inv(get_Kb(el)) + β_magma = inv(get_Kb(el_magma)) + creep_rock = LinearViscous(; η=1e21 * Pa * s) + creep_magma = LinearViscous(; η=1e20 * Pa * s) + g = 9.81m/s^2 + rheology = ( + #Name="UpperCrust" + SetMaterialParams(; + Phase = 1, + Density = PT_Density(; ρ0=2650kg / m^3, α=3e-5 / K, T0=0.0C, β=β_rock / Pa), + HeatCapacity = ConstantHeatCapacity(; Cp=1050J / kg / K), + Conductivity = ConstantConductivity(; k=3.0Watt / K / m), + LatentHeat = ConstantLatentHeat(; Q_L=350e3J / kg), + ShearHeat = ConstantShearheating(1.0NoUnits), + CompositeRheology = CompositeRheology((creep_rock, )), + Melting = MeltingParam_Caricchi(), + Gravity = ConstantGravity(; g=g), + Elasticity = el, + CharDim = CharDim, + ), + + #Name="Magma" + SetMaterialParams(; + Phase = 2, + Density = PT_Density(; ρ0=2650kg / m^3, T0=0.0C, β=β_magma / Pa), + HeatCapacity = ConstantHeatCapacity(; Cp=1050J / kg / K), + Conductivity = ConstantConductivity(; k=1.5Watt / K / m), + LatentHeat = ConstantLatentHeat(; Q_L=350e3J / kg), + ShearHeat = ConstantShearheating(0.0NoUnits), + CompositeRheology = CompositeRheology((creep_magma, )), + Melting = MeltingParam_Caricchi(), + Gravity = ConstantGravity(; g=g), + Elasticity = el_magma, + CharDim = CharDim, + ), + + # #Name="Sticky Air" + # SetMaterialParams(; + # Phase = 3, + # Density = ConstantDensity(ρ=1kg/m^3,), + # HeatCapacity = ConstantHeatCapacity(; Cp=1000J / kg / K), + # Conductivity = ConstantConductivity(; k=15Watt / K / m), + # LatentHeat = ConstantLatentHeat(; Q_L=0.0J / kg), + # ShearHeat = ConstantShearheating(0.0NoUnits), + # CompositeRheology = CompositeRheology((creep_air,)), + # Gravity = ConstantGravity(; g=g), + # CharDim = CharDim, + # ), + ) + +end + +function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = false) + + # Characteristic lengths for non dimensionalization + L_km = 100 + CharDim = GEO_units(;length=L_km, viscosity=1e21, temperature = 1e3C) + + #-------JustRelax parameters------------------------------------------------------------- + # Domain setup for JustRelax + L = nondimensionalize(L_km * km,CharDim) + sticky_air = nondimensionalize(0km, CharDim) # thickness of the sticky air layer + lz = L + sticky_air # domain length in y-direction + lx = ly = L # domain length in x-direction + li = lx, ly, lz # domain length in x- and y-direction + ni = nx, ny, nz # number of grid points in x- and y-direction + di = @. li / ni # grid step in x- and y-direction + origin = nondimensionalize(0.0km,CharDim), nondimensionalize(0.0km,CharDim), -lz # origin coordinates of the domain + grid = Geometry(ni, li; origin = origin) + (; xci, xvi) = grid # nodes at the center and vertices of the cells + εbg = nondimensionalize(0.0 / s, CharDim) # background strain rate + #--------------------------------------------------------------------------------------- + + # Physical Parameters + rheology = init_rheology(CharDim; is_compressible=true) + cutoff_visc = nondimensionalize((1e16Pa*s, 1e24Pa*s),CharDim) + κ = (4 / (rheology[1].HeatCapacity[1].Cp * rheology[1].Density[1].ρ0)) + dt = dt_diff = (0.5 * min(di...)^2 / κ / 2.01) # diffusive CFL timestep limiter + + # Initialize particles ------------------------------- + nxcell = 25 + max_xcell = 40 + min_xcell = 15 + particles = init_particles(backend, nxcell, max_xcell, min_xcell, xvi, di, ni) + subgrid_arrays = SubgridDiffusionCellArrays(particles) + # velocity grids + grid_vxi = velocity_grids(xci, xvi, di) + # temperature + pT, pPhases = init_cell_arrays(particles, Val(2)) + particle_args = (pT, pPhases) + + # Circular temperature anomaly-------------------------- + x_anomaly = lx * 0.5 + y_anomaly = ly * 0.5 + z_anomaly = -lz * 0.5 # origin of the small thermal anomaly + r_anomaly = nondimensionalize(5km, CharDim) # radius of perturbation + anomaly = nondimensionalize(50K, CharDim) # thermal perturbation (in K) + phase_ratios = PhaseRatio(backend_JR, ni, length(rheology)) + init_phases!(pPhases, particles, x_anomaly, y_anomaly, z_anomaly, r_anomaly, sticky_air, nondimensionalize(0.0km,CharDim), lz) + phase_ratios_center(phase_ratios, particles, grid, pPhases) + + # Initialisation of thermal profile + thermal = ThermalArrays(backend_JR, ni) # initialise thermal arrays and boundary conditions + thermal_bc = TemperatureBoundaryConditions(; + no_flux = (left = true, right = true, front = true, back = true, top = false, bot = false), + ) + @parallel (@idx ni .+ 1) init_T!( + thermal.T, + xvi[3], + sticky_air, + nondimensionalize(0e0km,CharDim), + lz, + nondimensionalize((723 - 273)K,CharDim) / lz, + nondimensionalize(273K,CharDim) + ) + circular_perturbation!( + thermal.T, anomaly, x_anomaly, y_anomaly, z_anomaly, r_anomaly, xvi, sticky_air + ) + thermal_bcs!(thermal.T, thermal_bc) + temperature2center!(thermal) + + # STOKES --------------------------------------------- + # Allocate arrays needed for every Stokes problem + stokes = StokesArrays(backend_JR, ni) # initialise stokes arrays with the defined regime + pt_stokes = PTStokesCoeffs(li, di; ϵ=1e-4, CFL=0.9 / √3.1) + # ---------------------------------------------------- + + flow_bcs = FlowBoundaryConditions(; + free_slip = (left=true, right=true, front=true, back=true, top=true, bot=true), + no_slip = (left=false, right=false, front=false, back=false, top=false, bot=false), + free_surface = true, + ) + flow_bcs!(stokes, flow_bcs) + update_halo!(@velocity(stokes)...) + + # Buoyancy force & viscosity + args = (; T=thermal.Tc, P=stokes.P, dt=Inf) + ρg = @zeros(ni...), @zeros(ni...), @zeros(ni...) # ρg[1] is the buoyancy force in the x direction, ρg[2] is the buoyancy force in the y direction + for _ in 1:5 + compute_ρg!(ρg[end], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + @parallel init_P!(stokes.P, ρg[3], xci[3], sticky_air) + end + compute_viscosity!(stokes, phase_ratios, args, rheology, cutoff_visc) + + pt_thermal = PTThermalCoeffs( + backend_JR, rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=0.8 / √3.1 + ) + + # Arguments for functions + @copy thermal.Told thermal.T + + # IO ------------------------------------------------ + # if it does not exist, make folder where figures are stored + if do_vtk + vtk_dir = joinpath(figdir, "vtk") + take(vtk_dir) + end + take(figdir) + # ---------------------------------------------------- + + # Plot initial T and η profiles + # let + # Zv = [z for _ in xvi[1], _ in xvi[2], z in xvi[3]][:] + # Z = [z for _ in xci[1], _ in xci[2], z in xci[3]][:] + # fig = Figure(; size=(1200, 900)) + # ax1 = Axis(fig[1, 1]; aspect=2 / 3, title="T") + # ax2 = Axis(fig[1, 2]; aspect=2 / 3, title="Pressure") + # scatter!( + # ax1, + # Array(ustrip.(dimensionalize(thermal.T[:], C, CharDim))), + # ustrip.(dimensionalize(Zv, km, CharDim)), + # ) + # scatter!( + # ax2, + # Array(ustrip.(dimensionalize(stokes.P[:], MPa, CharDim))), + # ustrip.(dimensionalize(Z, km, CharDim)), + # ) + # hideydecorations!(ax2) + # save(joinpath(figdir, "initial_profile.png"), fig) + # fig + # end + + # Time loop + t, it = 0.0, 0 + local Vx_v, Vy_v + if do_vtk + Vx_v = @zeros(ni .+ 1...) + Vy_v = @zeros(ni .+ 1...) + Vz_v = @zeros(ni .+ 1...) + end + + dt₀ = similar(stokes.P) + grid2particle!(pT, xvi, thermal.T, particles) + + @copy stokes.P0 stokes.P + @copy thermal.Told thermal.T + Tsurf = thermal.T[1, 1, end] + Tbot = thermal.T[1, 1, 1] + + while it < 25 + + # # Update buoyancy and viscosity - + # args = (; T=thermal.Tc, P=stokes.P, dt=Inf) + # compute_ρg!(ρg[end], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) + # compute_viscosity!(stokes, phase_ratios, args, rheology, cutoff_visc) + + # Stokes solver ----------------- + solve!( + stokes, + pt_stokes, + di, + flow_bcs, + ρg, + phase_ratios, + rheology, + args, + dt, + igg; + kwargs = (; + iterMax = 50e3, + free_surface = false, + nout = 1e3, + viscosity_cutoff = cutoff_visc, + ) + ) + tensor_invariant!(stokes.ε) + dt = compute_dt(stokes, di, dt_diff, igg) + # -------------------------------- + + # Thermal solver --------------- + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology, + args, + dt, + di; + kwargs =(; + igg = igg, + phase = phase_ratios, + iterMax = 150e3, + nout = 1e3, + verbose = true, + ) + ) + # subgrid diffusion + subgrid_characteristic_time!( + subgrid_arrays, particles, dt₀, phase_ratios, rheology, thermal, stokes, xci, di + ) + centroid2particle!(subgrid_arrays.dt₀, xci, dt₀, particles) + subgrid_diffusion!( + pT, thermal.T, thermal.ΔT, subgrid_arrays, particles, xvi, di, dt + ) + # ------------------------------ + + # Advection -------------------- + # advect particles in space + advection!(particles, RungeKutta2(), @velocity(stokes), grid_vxi, dt) + # advect particles in memory + move_particles!(particles, xvi, particle_args) + # check if we need to inject particles + inject_particles_phase!(particles, pPhases, (pT, ), (thermal.T,), xvi) + # update phase ratios + phase_ratios_center(phase_ratios, particles, grid, pPhases) + + particle2grid!(thermal.T, pT, xvi, particles) + @views thermal.T[:, :, end] .= Tsurf + @views thermal.T[:, :, 1] .= Tbot + thermal_bcs!(thermal.T, thermal_bc) + temperature2center!(thermal) + thermal.ΔT .= thermal.T .- thermal.Told + vertex2center!(thermal.ΔTc, thermal.ΔT) + + @show it += 1 + t += dt + + # # # Plotting ------------------------------------------------------- + if it == 1 || rem(it, 1) == 0 + (; η) = stokes.viscosity + # checkpointing(figdir, stokes, thermal.T, η, t) + + if igg.me == 0 + if do_vtk + velocity2vertex!(Vx_v, Vy_v, Vz_v, @velocity(stokes)...) + data_v = (; + T = Array(ustrip.(dimensionalize(thermal.T, C, CharDim))), + τxy= Array(ustrip.(dimensionalize(stokes.τ.xy, s^-1, CharDim))), + εxy= Array(ustrip.(dimensionalize(stokes.ε.xy, s^-1, CharDim))), + Vx = Array(ustrip.(dimensionalize(Vx_v,cm/yr,CharDim))), + Vy = Array(ustrip.(dimensionalize(Vy_v, cm/yr, CharDim))), + Vz = Array(ustrip.(dimensionalize(Vz_v, cm/yr, CharDim))), + ) + data_c = (; + P = Array(ustrip.(dimensionalize(stokes.P,MPa,CharDim))), + τxx = Array(ustrip.(dimensionalize(stokes.τ.xx, MPa,CharDim))), + τyy = Array(ustrip.(dimensionalize(stokes.τ.yy,MPa,CharDim))), + τzz = Array(ustrip.(dimensionalize(stokes.τ.zz,MPa,CharDim))), + τII = Array(ustrip.(dimensionalize(stokes.τ.II, MPa, CharDim))), + εxx = Array(ustrip.(dimensionalize(stokes.ε.xx, s^-1,CharDim))), + εyy = Array(ustrip.(dimensionalize(stokes.ε.yy, s^-1,CharDim))), + εzz = Array(ustrip.(dimensionalize(stokes.ε.zz, s^-1,CharDim))), + εII = Array(ustrip.(dimensionalize(stokes.ε.II, s^-1,CharDim))), + η = Array(ustrip.(dimensionalize(η,Pa*s,CharDim))), + ) + save_vtk( + joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), + xvi, + xci, + data_v, + data_c, + ) + end + + let + Zv = [z for _ in 1:nx+1, _ in 1:ny+1, z in ustrip.(dimensionalize(xvi[3],km,CharDim))][:] + Z = [z for _ in 1:nx , _ in 1:ny , z in ustrip.(dimensionalize(xci[3],km,CharDim))][:] + fig = Figure(; size=(1200, 900)) + ax1 = Axis(fig[1, 1]; aspect = 2 / 3, title="T") + ax2 = Axis(fig[1, 2]; aspect = 2 / 3, title="Pressure") + a3 = Axis(fig[2, 1]; aspect = 2 / 3, title="τII") + + scatter!(ax1, ustrip.(dimensionalize((Array(thermal.T)), C, CharDim))[:] , Zv, markersize = 4) + scatter!(ax2, ustrip.(dimensionalize((Array(stokes.P)), MPa, CharDim))[:] , Z , markersize = 4) + scatter!(a3, ustrip.(dimensionalize(Array(stokes.τ.II), MPa, CharDim))[:], Z , markersize = 4) + + hideydecorations!(ax2) + save(joinpath(figdir, "pressure_profile_$it.png"), fig) + fig + end + end + end + end + + finalize_global_grid() + return nothing +end + +figdir = "Thermal_stresses_around_cooling_magma_3D" +do_vtk = true # set to true to generate VTK files for ParaView +n = 16 +nx = n +ny = n +nz = n +igg = if !(JustRelax.MPI.Initialized()) # initialize (or not) MPI grid + IGG(init_global_grid(nx, ny, nz; init_MPI=true)...) +else + igg +end + +# run main script +# main3D(igg; figdir=figdir, nx=nx, ny=ny, nz=nz, do_vtk = do_vtk); From e321be0f492967f2c1611354f9d4ff916571d3cd Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 16 May 2024 14:05:03 +0200 Subject: [PATCH 50/60] fix geotherm --- subduction/Buiter/Buiter_setup2D_sticky.jl | 36 +++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/subduction/Buiter/Buiter_setup2D_sticky.jl b/subduction/Buiter/Buiter_setup2D_sticky.jl index 57323a93..b90564bf 100644 --- a/subduction/Buiter/Buiter_setup2D_sticky.jl +++ b/subduction/Buiter/Buiter_setup2D_sticky.jl @@ -6,12 +6,13 @@ function GMG_subduction_2D(nx, ny) nx, nz = nx, ny Tbot = 1474.0 x = range(0, 3000, nx); - z = range(-model_depth, 10, nz); + air_thickness = 10.0 + z = range(-model_depth, air_thickness, nz); Grid2D = CartData(xyz_grid(x,0,z)) Phases = zeros(Int64, nx, 1, nz); Temp = fill(Tbot, nx, 1, nz); - air_thickness = 20.0 - lith = LithosphericPhases(Layers=[80], Phases=[1 0]) + Tlab = 1300 + # lith = LithosphericPhases(Layers=[80], Phases=[1 0], Tlab=Tlab) # phases # 0: asthenosphere @@ -26,8 +27,8 @@ function GMG_subduction_2D(nx, ny) xlim =(0, 3000), zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, - phase = LithosphericPhases(Layers=[], Phases=[0]), - T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) + phase = LithosphericPhases(Layers=[], Phases=[0], Tlab=Tlab), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=50, Adiabat=0) ) # Add left oceanic plate @@ -38,8 +39,8 @@ function GMG_subduction_2D(nx, ny) xlim =(100, 3000-100), zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, - phase = lith, - T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) + phase = LithosphericPhases(Layers=[80], Phases=[1 0], Tlab=Tlab), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=50, Adiabat=0) ) # Add right oceanic plate crust @@ -50,8 +51,8 @@ function GMG_subduction_2D(nx, ny) xlim =(3000-1430, 3000-200), zlim =(-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, - phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0]), - T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) + phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0], Tlab=Tlab), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=50, Adiabat=0) ) # Add slab @@ -62,17 +63,9 @@ function GMG_subduction_2D(nx, ny) xlim = (3000-1430, 3000-1430-250), zlim =(-80, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=-30, - phase = LithosphericPhases(Layers=[8 80], Phases=[2 1 0]), #, Tlab=Tbot ), - T = HalfspaceCoolingTemp( - Tsurface = 20, - Tmantle = Tbot, - Age = 80, - ) + phase = LithosphericPhases(Layers=[8 80], Phases=[2 1 0], Tlab=Tlab), + T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=50, Adiabat=0) ) - heatmap(x,z,Temp[:,1,:]) - # Lithosphere-asthenosphere boundary: - # ind = findall(Temp .> 1250 .&& (Phases.==2 .|| Phases.==5)); - # Phases[ind] .= 0; surf = Grid2D.z.val .> 0.0 Temp[surf] .= 20.0 @@ -83,7 +76,8 @@ function GMG_subduction_2D(nx, ny) li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 origin = (x[1], z[1]) .* 1e3 - ph = Phases[:,1,:] .+ 1 + ph = Phases[:,1,:] .+ 1 + T = Temp[:,1,:] - return li, origin, ph, Temp[:,1,:].+273 + return li, origin, ph, T end \ No newline at end of file From bb75b19bcbcf3e9a0a22f71a33370d285291964f Mon Sep 17 00:00:00 2001 From: albert-de-montserrat Date: Thu, 16 May 2024 14:05:10 +0200 Subject: [PATCH 51/60] docs --- docs/src/man/subduction2d/setup.md | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/src/man/subduction2d/setup.md b/docs/src/man/subduction2d/setup.md index 3911103a..a4ff3670 100644 --- a/docs/src/man/subduction2d/setup.md +++ b/docs/src/man/subduction2d/setup.md @@ -15,7 +15,7 @@ Phases = zeros(Int64, nx, 1, nz); Temp = fill(Tbot, nx, 1, nz); ``` -In this model we have four material phases given by: +In this model we have four material phases with their own phase numbers: | Material | Phase number | | :---------------- | :----------: | @@ -37,6 +37,7 @@ add_box!( T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80,Adiabat=0.4) ) ``` +![](setup_1.png) Next we add a horizontal 80km thick oceanic lithosphere. Note that we leave a 100km buffer zone next to the vertical boundaries of the domain, to facilitate the sliding of the oceanic plates. ```julia @@ -50,6 +51,7 @@ add_box!( T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) ) ``` +![](setup_2.png) As in the original paper, we add a 8km thick crust on top of the subducting oceanic plate. ```julia @@ -65,6 +67,7 @@ add_box!( T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) ) ``` +![](setup_3.png) And finally we add the subducting slab, whith the trench located at 1430km from the right-hand-side boundary. @@ -80,20 +83,18 @@ add_box!( T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) ) ``` +![](setup_4.png) ```julia - heatmap(x,z,Temp[:,1,:]) - - surf = Grid2D.z.val .> 0.0 - Temp[surf] .= 20.0 - Phases[surf] .= 3 - - Grid2D = addfield(Grid2D,(;Phases, Temp)) - - li = (abs(last(x)-first(x)), abs(last(z)-first(z))).* 1e3 - origin = (x[1], z[1]) .* 1e3 - - ph = Phases[:,1,:] .+ 1 +surf = Grid2D.z.val .> 0.0 +@views Temp[surf] .= 20.0 +@views Phases[surf] .= 3 +``` +![](setup_5.png) - li, origin, ph, Temp[:,1,:].+273 -``` \ No newline at end of file +```julia +li = (abs(last(x)-first(x)), abs(last(z)-first(z))) .* 1e3 # in meters +origin = (x[1], z[1]) .* 1e3 # lower-left corner of the domain +Phases = Phases[:,1,:] .+ 1 # +1 becayse Julia is 1-indexed +Temp = Temp[:,1,:].+273 # in Kelvin +``` \ No newline at end of file From 13ff3c167ee915812335c263664c210ae8c9d5d2 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 21 May 2024 10:56:08 +0200 Subject: [PATCH 52/60] lift dimension constrain --- src/boundaryconditions/BoundaryConditions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index e85489c1..b6be4cee 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -5,7 +5,7 @@ include("no_slip.jl") include("pure_shear.jl") @inline bc_index(x::NTuple{2,T}) where {T} = mapreduce(xi -> max(size(xi)...), max, x) -@inline bc_index(x::T) where {T<:AbstractArray{<:Any,2}} = max(size(x)...) +@inline bc_index(x::T) where {T<:AbstractArray} = max(size(x)...) @inline function bc_index(x::NTuple{3,T}) where {T} n = mapreduce(xi -> max(size(xi)...), max, x) From f7f6623913134ff08311ae49a69f5634341ba596 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 21 May 2024 11:05:53 +0200 Subject: [PATCH 53/60] typos --- docs/src/man/equations.md | 2 +- docs/src/man/subduction2d/setup.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/man/equations.md b/docs/src/man/equations.md index d11fa32c..303d75d3 100644 --- a/docs/src/man/equations.md +++ b/docs/src/man/equations.md @@ -20,7 +20,7 @@ The pseudo-transient method consists in augmenting the right-hand-side of the ta ## Stokes -The psuedo-transient formulation of the Stokes equations yields: +The pseudo-transient formulation of the Stokes equations yields: $$\widetilde{\rho}\frac{\partial \boldsymbol{u}}{\partial \psi} + \nabla\cdot\boldsymbol{\tau} - \nabla p = \boldsymbol{f}$$ diff --git a/docs/src/man/subduction2d/setup.md b/docs/src/man/subduction2d/setup.md index a4ff3670..e9e73f63 100644 --- a/docs/src/man/subduction2d/setup.md +++ b/docs/src/man/subduction2d/setup.md @@ -69,7 +69,7 @@ add_box!( ``` ![](setup_3.png) -And finally we add the subducting slab, whith the trench located at 1430km from the right-hand-side boundary. +And finally we add the subducting slab, with the trench located at 1430km from the right-hand-side boundary. ```julia add_box!( @@ -95,6 +95,6 @@ surf = Grid2D.z.val .> 0.0 ```julia li = (abs(last(x)-first(x)), abs(last(z)-first(z))) .* 1e3 # in meters origin = (x[1], z[1]) .* 1e3 # lower-left corner of the domain -Phases = Phases[:,1,:] .+ 1 # +1 becayse Julia is 1-indexed +Phases = Phases[:,1,:] .+ 1 # +1 because Julia is 1-indexed Temp = Temp[:,1,:].+273 # in Kelvin ``` \ No newline at end of file From 0a4778f7851820b5440fe4b5316785c94051ef43 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 21 May 2024 11:06:01 +0200 Subject: [PATCH 54/60] bump GP --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index d13de3db..8c3ba8dd 100644 --- a/Project.toml +++ b/Project.toml @@ -31,7 +31,7 @@ AMDGPU = "0.6, 0.7, 0.8" Adapt = "3" CUDA = "4.4.1, 5" CellArrays = "0.2" -GeoParams = "0.5.8" +GeoParams = "0.6" HDF5 = "0.17.1" ImplicitGlobalGrid = "0.15.0" MPI = "0.20" From be2757f4de969641030a5ba22114484ee2459e2b Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 21 May 2024 11:39:20 +0200 Subject: [PATCH 55/60] docs --- docs/src/man/ShearBands.md | 3 +- docs/src/man/subduction2d/rheology.md | 76 +++++++++++++++++++++++ docs/src/man/subduction2d/setup.md | 25 ++++---- docs/src/man/subduction2d/subduction2D.md | 3 +- 4 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 docs/src/man/subduction2d/rheology.md diff --git a/docs/src/man/ShearBands.md b/docs/src/man/ShearBands.md index 33273296..3e3fca49 100644 --- a/docs/src/man/ShearBands.md +++ b/docs/src/man/ShearBands.md @@ -10,7 +10,6 @@ using JustRelax, JustRelax.JustRelax2D, JustRelax.DataIO const backend_JR = CPUBackend ``` - We will also use `ParallelStencil.jl` to write some device-agnostic helper functions: ```julia using ParallelStencil @@ -54,7 +53,7 @@ dt = η0/G0/4.0 # assumes Maxwell time of 4 el_bg = ConstantElasticity(; G=G0, Kb=4) el_inc = ConstantElasticity(; G=Gi, Kb=4) visc = LinearViscous(; η=η0) -pl = DruckerPrager_regularised(; # non-regularized plasticity +pl = DruckerPrager_regularised(; # regularized plasticity C = C, ϕ = ϕ, η_vp = η_reg, diff --git a/docs/src/man/subduction2d/rheology.md b/docs/src/man/subduction2d/rheology.md new file mode 100644 index 00000000..3a7b9282 --- /dev/null +++ b/docs/src/man/subduction2d/rheology.md @@ -0,0 +1,76 @@ +# Using GeoParams.jl to define the rheology of the material phases + +We will use the same physical parameters as the ones defined in [Hummel, et al 2024](https://doi.org/10.5194/se-15-567-2024). + +The thermal expansion coefficient $\alpha$ and heat capacity $C_p$ are the same for all the material phases + +```julia +α = 2.4e-5 # 1 / K +Cp = 750 # J / kg K +``` + +The density of all the phases is constant, except for the oceanic lithosphere, which uses the pressure and temperature dependent equation of state $\rho = \rho_0 \left(1 - \alpha (T-T_0) - \beta (P-P_0) \right)$, where $\rho_0 = \rho (T=1475 \text{C}^{\circ})=3200 \text{kg/m}^3$.which corresponds to the `PT_Density` object from `GeoParams.jl`: + +```julia +density_lithosphere = PT_Density(; ρ0=3.2e3, α = α, β = 0e0, T0 = 273+1474) +``` + +We will run the case where the rheology is given by a combination of dislocation and diffusion creep for wet olivine, + +```julia +disl_wet_olivine = SetDislocationCreep(Dislocation.wet_olivine1_Hirth_2003) +diff_wet_olivine = SetDiffusionCreep(Diffusion.wet_olivine_Hirth_2003) +``` + +and where plastic failure is given by the formulation from [Duretz et al, 2021](https://doi.org/10.1029/2021GC009675) +```julia +# non-regularized plasticity +ϕ = asind(0.1) +C = 1e6 # Pa +plastic_model = DruckerPrager_regularised(; C = C, ϕ = ϕ, η_vp=η_reg, Ψ=0.0) +``` + +Finally we define the rheology objects of `GeoParams.jl` +```julia +rheology = ( + SetMaterialParams(; + Name = "Asthenoshpere", + Phase = 1, + Density = ConstantDensity(; ρ=3.2e3), + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + CompositeRheology = CompositeRheology( (LinearViscous(; η=1e20),)), + Gravity = ConstantGravity(; g=9.81), + ), + SetMaterialParams(; + Name = "Oceanic lithosphere", + Phase = 2, + Density = density_lithosphere, + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + CompositeRheology = CompositeRheology( + ( + disl_wet_olivine, + diff_wet_olivine, + plastic_model, + ) + ), + ), + SetMaterialParams(; + Name = "oceanic crust", + Phase = 3, + Density = ConstantDensity(; ρ=3.2e3), + HeatCapacity = ConstantHeatCapacity(; Cp = Cp), + Conductivity = ConstantConductivity(; k = 2.5), + CompositeRheology = CompositeRheology( (LinearViscous(; η=1e20),)), + ), + SetMaterialParams(; + Name = "StickyAir", + Phase = 4, + Density = ConstantDensity(; ρ=1), # water density + HeatCapacity = ConstantHeatCapacity(; Cp = 1e34), + Conductivity = ConstantConductivity(; k = 3), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e19),)), + ), +) +``` \ No newline at end of file diff --git a/docs/src/man/subduction2d/setup.md b/docs/src/man/subduction2d/setup.md index e9e73f63..2247b93c 100644 --- a/docs/src/man/subduction2d/setup.md +++ b/docs/src/man/subduction2d/setup.md @@ -4,11 +4,12 @@ As described in the original [paper](https://doi.org/10.5194/se-15-567-2024), th We will use GeophysicalModelGenerator.jl to generate the initial geometry, material phases, and thermal field of our models. We will start by defining the dimensions and resolution of our model, as well as initializing the `Grid2D` object and two arrays `Phases` and `Temp` that host the material phase (given by an integer) and the thermal field, respectively. ```julia -nx, nz = 512, 218 -Tbot = 1474.0 # [Celsius] -model_depth = 660 -air_thickness = 10 -x = range(0, 3000, nx); +nx, nz = 512, 218 # number of cells per dimension +Tbot = 1474.0 # [Celsius] +model_depth = 660 # [km] +air_thickness = 10 # [km] +Lx = 3000 # model length [km] +x = range(0, Lx, nx); z = range(-model_depth, air_thickness, nz); Grid2D = CartData(xyz_grid(x,0,z)) Phases = zeros(Int64, nx, 1, nz); @@ -31,8 +32,8 @@ add_box!( Phases, Temp, Grid2D; - xlim =(0, 3000), - zlim =(-model_depth, 0.0), + xlim = (0, Lx), + zlim = (-model_depth, 0.0), phase = LithosphericPhases(Layers=[], Phases=[0]), T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80,Adiabat=0.4) ) @@ -45,8 +46,8 @@ add_box!( Phases, Temp, Grid2D; - xlim =(100, 3000-100), # with 100 km buffer zones - zlim =(-model_depth, 0.0), + xlim = (100, Lx-100), # 100 km buffer zones on both sides + zlim = (-model_depth, 0.0), phase = LithosphericPhases(Layers=[80], Phases=[1 0]), T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) ) @@ -60,8 +61,8 @@ add_box!( Phases, Temp, Grid2D; - xlim =(3000-1430, 3000-200), - zlim =(-model_depth, 0.0), + xlim = (Lx-1430, Lx-200), + zlim = (-model_depth, 0.0), Origin = nothing, StrikeAngle=0, DipAngle=0, phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0]), T = HalfspaceCoolingTemp(Tsurface=20, Tmantle=Tbot, Age=80, Adiabat=0.4) @@ -76,7 +77,7 @@ add_box!( Phases, Temp, Grid2D; - xlim = (3000-1430, 3000-1430-250), + xlim = (Lx-1430, Lx-1430-250), zlim = (-80, 0.0), Origin = (nothing, StrikeAngle=0, DipAngle=-30), phase = LithosphericPhases(Layers=[8 72], Phases=[2 1 0]), diff --git a/docs/src/man/subduction2d/subduction2D.md b/docs/src/man/subduction2d/subduction2D.md index 31b75c06..ed55ec69 100644 --- a/docs/src/man/subduction2d/subduction2D.md +++ b/docs/src/man/subduction2d/subduction2D.md @@ -244,5 +244,4 @@ fig ``` ### Final model -Shear Bands evolution in a 2D visco-elasto-plastic rheology model -![Shearbands](../assets/movies/DP_nx2058_2D.gif) +![](990.png) From 43d47cd955330ac66a0eb83a80ca8871dfb97b5a Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 21 May 2024 11:39:44 +0200 Subject: [PATCH 56/60] format --- src/boundaryconditions/BoundaryConditions.jl | 2 - src/ext/CUDA/2D.jl | 4 +- src/ext/CUDA/3D.jl | 4 +- src/phases/phases.jl | 6 ++- src/stokes/MetaStokes.jl | 8 +--- src/stokes/Stokes2D.jl | 15 +++--- src/stokes/StressRotationParticles.jl | 50 +++++++++----------- src/stokes/VorticityKernels.jl | 4 +- src/types/stokes.jl | 4 +- 9 files changed, 46 insertions(+), 51 deletions(-) diff --git a/src/boundaryconditions/BoundaryConditions.jl b/src/boundaryconditions/BoundaryConditions.jl index b6be4cee..81968a6a 100644 --- a/src/boundaryconditions/BoundaryConditions.jl +++ b/src/boundaryconditions/BoundaryConditions.jl @@ -59,5 +59,3 @@ function _flow_bcs!(bcs, V) return nothing end - - diff --git a/src/ext/CUDA/2D.jl b/src/ext/CUDA/2D.jl index f523b1dc..29454e4d 100644 --- a/src/ext/CUDA/2D.jl +++ b/src/ext/CUDA/2D.jl @@ -168,7 +168,9 @@ function JR2D.center2vertex!( return center2vertex!(vertex_yz, vertex_xz, vertex_xy, center_yz, center_xz, center_xy) end -function JR2D.velocity2vertex!(Vx_v::CuArray, Vy_v::CuArray, Vx::CuArray, Vy::CuArray; ghost_nodes=true) +function JR2D.velocity2vertex!( + Vx_v::CuArray, Vy_v::CuArray, Vx::CuArray, Vy::CuArray; ghost_nodes=true +) velocity2vertex!(Vx_v, Vy_v, Vx, Vy; ghost_nodes=ghost_nodes) return nothing end diff --git a/src/ext/CUDA/3D.jl b/src/ext/CUDA/3D.jl index 2ae5c500..fb0308ca 100644 --- a/src/ext/CUDA/3D.jl +++ b/src/ext/CUDA/3D.jl @@ -170,7 +170,9 @@ function JR3D.center2vertex!( return center2vertex!(vertex_yz, vertex_xz, vertex_xy, center_yz, center_xz, center_xy) end -function JR3D.velocity2vertex!(Vx_v::CuArray, Vy_v::CuArray, Vz_v::CuArray, Vx::CuArray, Vy::CuArray, Vz::CuArray) +function JR3D.velocity2vertex!( + Vx_v::CuArray, Vy_v::CuArray, Vz_v::CuArray, Vx::CuArray, Vy::CuArray, Vz::CuArray +) velocity2vertex!(Vx_v, Vy_v, Vz_v, Vx, Vy, Vz) return nothing end diff --git a/src/phases/phases.jl b/src/phases/phases.jl index 710f789d..81dea637 100644 --- a/src/phases/phases.jl +++ b/src/phases/phases.jl @@ -49,7 +49,9 @@ end end function phase_ratios_center!(phase_ratios, particles, grid, phases) - return phase_ratios_center!(backend(phase_ratios), phase_ratios, particles, grid, phases) + return phase_ratios_center!( + backend(phase_ratios), phase_ratios, particles, grid, phases + ) end function phase_ratios_center!( @@ -96,7 +98,7 @@ function phase_ratio_weights( for i in eachindex(ph) # bilinear weight (1-(xᵢ-xc)/dx)*(1-(yᵢ-yc)/dy) - p = getindex.(pxi, i) + p = getindex.(pxi, i) isnan(first(p)) && continue x = @inline bilinear_weight(cell_center, p, di) sumw += x # reduce diff --git a/src/stokes/MetaStokes.jl b/src/stokes/MetaStokes.jl index 8438074a..ce644e4d 100644 --- a/src/stokes/MetaStokes.jl +++ b/src/stokes/MetaStokes.jl @@ -19,7 +19,6 @@ function make_velocity_struct!(ndim::Integer; name::Symbol=:Velocity) function $(name)(ni::NTuple{3,T}) where {T} return new{$PTArray}(@zeros(ni[1]...), @zeros(ni[2]...), @zeros(ni[3]...)) end - end $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = $(name)(args...) end @@ -71,9 +70,9 @@ function make_vorticity_struct!(ndim::Integer) return new{$PTArray}(yz_c, xz_c, xy_c, yz_v, xz_v, xy_v) end - end - VorticityTensor(args::Vararg{T,N}) where {T<:AbstractArray,N} = VorticityTensor(args...) + VorticityTensor(args::Vararg{T,N}) where {T<:AbstractArray,N} = + VorticityTensor(args...) end end @@ -134,7 +133,6 @@ function make_symmetrictensor_struct!(nDim::Integer; name::Symbol=:SymmetricTens @zeros(ni...), # II (second invariant) ) end - end $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = $(name)(args...) @@ -163,7 +161,6 @@ function make_residual_struct!(ndim; name::Symbol=:Residual) RP = @zeros(ni[4]...) return new{typeof(Rx)}(Rx, Ry, Rz, RP) end - end $(name)(args::Vararg{T,N}) where {T<:AbstractArray,N} = $(name)(args...) end @@ -296,7 +293,6 @@ function make_stokes_struct!(; name::Symbol=:StokesArrays) args... ) end - end end end diff --git a/src/stokes/Stokes2D.jl b/src/stokes/Stokes2D.jl index 8b39ba05..bd27216b 100644 --- a/src/stokes/Stokes2D.jl +++ b/src/stokes/Stokes2D.jl @@ -504,15 +504,14 @@ function _solve!( Aij .= 0.0 end Vx_on_Vy = @zeros(size(stokes.V.Vy)) - compute_viscosity!(stokes, phase_ratios, args, rheology, viscosity_cutoff) compute_ρg!(ρg[2], phase_ratios, rheology, args) - + (; ρbg) = args - ρgz_diff = ρg[2] .- ρbg - Plitho = reverse(cumsum(reverse((ρg[2]) .* di[2], dims=2), dims=2), dims=2) - + ρgz_diff = ρg[2] .- ρbg + Plitho = reverse(cumsum(reverse((ρg[2]) .* di[2]; dims=2); dims=2); dims=2) + while iter ≤ iterMax iterMin < iter && err < ϵ && break @@ -535,7 +534,7 @@ function _solve!( θ_dτ, args, ) - args.P .= stokes.P .+ Plitho .- minimum(stokes.P) + args.P .= stokes.P .+ Plitho .- minimum(stokes.P) # stokes.P[1, 1] = stokes.P[2, 1] # stokes.P[end, 1] = stokes.P[end - 1, 1] @@ -548,7 +547,7 @@ function _solve!( @parallel (@idx ni .+ 1) compute_strain_rate!( @strain(stokes)..., stokes.∇V, @velocity(stokes)..., _di... ) - + if rem(iter, nout) == 0 @copy η0 η end @@ -670,7 +669,7 @@ function _solve!( # @parallel (@idx ni .+ 1) multi_copy!(@tensor(stokes.τ_o), @tensor(stokes.τ)) # @parallel (@idx ni) multi_copy!( - # @tensor_center(stokes.τ_o), @tensor_center(stokes.τ) + # @tensor_center(stokes.τ_o), @tensor_center(stokes.τ) # ) return ( diff --git a/src/stokes/StressRotationParticles.jl b/src/stokes/StressRotationParticles.jl index 9b2b55e5..5f695de6 100644 --- a/src/stokes/StressRotationParticles.jl +++ b/src/stokes/StressRotationParticles.jl @@ -14,7 +14,7 @@ end for ip in JustRelax.cellaxes(index) !@cell(index[ip, cell...]) && continue # no particle in this location - ω_xy = @cell ω[ip, cell...] + ω_xy = @cell ω[ip, cell...] τ_xx = @cell xx[ip, cell...] τ_yy = @cell yy[ip, cell...] τ_xy = @cell xy[ip, cell...] @@ -34,9 +34,7 @@ function rotation_matrix!(xx, yy, xy, ω, index, dt) return nothing end -@parallel_indices (i, j) function _rotation_matrix!( - xx, yy, xy, ω, index, dt -) +@parallel_indices (i, j) function _rotation_matrix!(xx, yy, xy, ω, index, dt) cell = i, j for ip in JustRelax.cellaxes(index) @@ -74,36 +72,36 @@ end function rotate_stress_particles!( stokes::JustRelax.StokesArrays, - τxx_vertex::T, + τxx_vertex::T, τyy_vertex::T, - τxx_o_vertex::T, + τxx_o_vertex::T, τyy_o_vertex::T, - τxx_p::CA, - τyy_p::CA, + τxx_p::CA, + τyy_p::CA, τxy_p::CA, vorticity_p::CA, particles, grid::JustRelax.Geometry, dt; - fn = rotation_matrix! -) where {T<:AbstractArray, CA} - (;xvi, xci) = grid + fn=rotation_matrix!, +) where {T<:AbstractArray,CA} + (; xvi, xci) = grid nx, ny = size(τxx_p) # interpolate stresses to particles for (src, src_o, dst_v, dst_v_o, dst_p) in zip( - @tensor_center(stokes.τ), - @tensor_center(stokes.τ_o), + @tensor_center(stokes.τ), + @tensor_center(stokes.τ_o), (τxx_vertex, τyy_vertex, stokes.τ.xy), (τxx_o_vertex, τyy_o_vertex, stokes.τ_o.xy), - (τxx_p, τyy_p, τxy_p) + (τxx_p, τyy_p, τxy_p), ) @parallel center2vertex!(dst_v, src) @parallel center2vertex!(dst_v_o, src_o) - @parallel (1:nx + 1) free_slip_y!(dst_v) - @parallel (1:ny + 1) free_slip_x!(dst_v) - @parallel (1:nx + 1) free_slip_y!(dst_v_o) - @parallel (1:ny + 1) free_slip_x!(dst_v_o) + @parallel (1:(nx + 1)) free_slip_y!(dst_v) + @parallel (1:(ny + 1)) free_slip_x!(dst_v) + @parallel (1:(nx + 1)) free_slip_y!(dst_v_o) + @parallel (1:(ny + 1)) free_slip_x!(dst_v_o) grid2particle_flip!(dst_p, xvi, dst_v, dst_v_o, particles) end @@ -112,17 +110,13 @@ function rotate_stress_particles!( @parallel center2vertex!(stokes.ω_o.xy_v, stokes.ω_o.xy_c) @parallel (1:nx) free_slip_y!(stokes.ω.xy_c) @parallel (1:ny) free_slip_x!(stokes.ω.xy_c) - @parallel (1:nx+1) free_slip_y!(stokes.ω_o.xy_v) - @parallel (1:ny+1) free_slip_x!(stokes.ω_o.xy_v) + @parallel (1:(nx + 1)) free_slip_y!(stokes.ω_o.xy_v) + @parallel (1:(ny + 1)) free_slip_x!(stokes.ω_o.xy_v) grid2particle_flip!(vorticity_p, xvi, stokes.ω.xy_v, stokes.ω_o.xy_v, particles) # rotate old stress - fn( - τxx_p, τyy_p, τxy_p, vorticity_p, particles.index, dt - ) + fn(τxx_p, τyy_p, τxy_p, vorticity_p, particles.index, dt) # interpolate old stress to grid arrays - particle2grid_centroid!(stokes.τ_o.xx, τxx_p, xci, particles) - particle2grid_centroid!(stokes.τ_o.yy, τyy_p, xci, particles) - particle2grid_centroid!(stokes.τ_o.xy_c, τxy_p, xci, particles) + particle2grid_centroid!(stokes.τ_o.xx, τxx_p, xci, particles) + particle2grid_centroid!(stokes.τ_o.yy, τyy_p, xci, particles) + return particle2grid_centroid!(stokes.τ_o.xy_c, τxy_p, xci, particles) end - - diff --git a/src/stokes/VorticityKernels.jl b/src/stokes/VorticityKernels.jl index 9c09407a..422d1a43 100644 --- a/src/stokes/VorticityKernels.jl +++ b/src/stokes/VorticityKernels.jl @@ -5,7 +5,7 @@ function compute_vorticity!(::CPUBackendTrait, stokes, di) ω_xy = stokes.ω.xy ni = size(ω_xy) @parallel (@idx ni) compute_vorticity!(ω_xy, @velocity(stokes)..., inv.(di)...) - + return nothing end @@ -16,4 +16,4 @@ end ω_xy[i, j] = 0.5 * (dx(Vy) - dy(Vx)) return nothing -end \ No newline at end of file +end diff --git a/src/types/stokes.jl b/src/types/stokes.jl index 65c162aa..8cc1de59 100644 --- a/src/types/stokes.jl +++ b/src/types/stokes.jl @@ -41,7 +41,9 @@ struct Vorticity{T} xz::Union{T,Nothing} xy::T - Vorticity(yz::Union{T,Nothing}, xz::Union{T,Nothing}, xy::T) where {T} = new{T}(yz, xz, xy) + function Vorticity(yz::Union{T,Nothing}, xz::Union{T,Nothing}, xy::T) where {T} + return new{T}(yz, xz, xy) + end end Vorticity(nx::T, ny::T) where {T<:Number} = Vorticity((nx, ny)) From 8541b0173253649329e438d3d530aadb3dcc464a Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Tue, 21 May 2024 11:41:48 +0200 Subject: [PATCH 57/60] up _typos.toml --- _typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/_typos.toml b/_typos.toml index ac343ee7..6e0f2329 100644 --- a/_typos.toml +++ b/_typos.toml @@ -2,3 +2,4 @@ Ths = "Ths" oly = "oly" iy = "iy" +nd = "nd" From 19eb749cd8257578940aeee5bfa3786263ef4f2a Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Fri, 24 May 2024 17:13:39 +0200 Subject: [PATCH 58/60] up scripts --- Blober.jl | 139 ++++++++++++++++++--------- docs/src/man/subduction2d/setup.md | 4 +- subduction/Buiter/Buiter2D.jl | 17 +++- subduction/Buiter/Buiter_rheology.jl | 8 +- 4 files changed, 115 insertions(+), 53 deletions(-) diff --git a/Blober.jl b/Blober.jl index be06792f..f9dae2b8 100644 --- a/Blober.jl +++ b/Blober.jl @@ -1,37 +1,52 @@ +const isCUDA = false +# const isCUDA = true + +@static if isCUDA + using CUDA +end + using JustRelax, JustRelax.JustRelax3D, JustRelax.DataIO import JustRelax.@cell -const backend_JR = CPUBackend + +const backend_JR = @static if isCUDA + CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +else + JustRelax.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +end using ParallelStencil, ParallelStencil.FiniteDifferences3D -@init_parallel_stencil(Threads, Float64, 3) #or (CUDA, Float64, 2) or (AMDGPU, Float64, 2) -using JustPIC -using JustPIC._3D +@static if isCUDA + @init_parallel_stencil(CUDA, Float64, 3) +else + @init_parallel_stencil(Threads, Float64, 3) +end + +using JustPIC, JustPIC._3D # Threads is the default backend, # to run on a CUDA GPU load CUDA.jl (i.e. "using CUDA") at the beginning of the script, # and to run on an AMD GPU load AMDGPU.jl (i.e. "using AMDGPU") at the beginning of the script. -const backend = JustPIC.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +const backend = @static if isCUDA + CUDABackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +else + JustPIC.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend +end -using Printf, Statistics, LinearAlgebra, GeoParams, CellArrays -using StaticArrays -using ImplicitGlobalGrid -using MPI: MPI -using WriteVTK -# using GLMakie +using GeoParams, GLMakie, CellArrays ## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- -import ParallelStencil.INDICES -const idx_j = INDICES[3] -macro all_j(A) - return esc(:($A[$idx_j])) -end +# import ParallelStencil.INDICES +# const idx_j = INDICES[3] +# macro all_j(A) +# return esc(:($A[$idx_j])) +# end -@parallel function init_P!(P, ρg, z, sticky_air) - @all(P) = @all(ρg) - # @all(P) = abs(@all(ρg) * (@all_j(z) + sticky_air)) * <((@all_j(z) + sticky_air), 0.0) - return nothing -end +# @parallel function init_P!(P, ρg, z, sticky_air) +# @all(P) = @all(ρg) +# # @all(P) = abs(@all(ρg) * (@all_j(z) + sticky_air)) * <((@all_j(z) + sticky_air), 0.0) +# return nothing +# end function init_phases!(phases, particles, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, sticky_air,top, bottom) ni = size(phases) @@ -128,14 +143,15 @@ function init_rheology(CharDim; is_compressible = false) end β_rock = inv(get_Kb(el)) β_magma = inv(get_Kb(el_magma)) - creep_rock = LinearViscous(; η=1e21 * Pa * s) + creep_rock = LinearViscous(; η=1e20 * Pa * s) creep_magma = LinearViscous(; η=1e20 * Pa * s) g = 9.81m/s^2 rheology = ( #Name="UpperCrust" SetMaterialParams(; Phase = 1, - Density = PT_Density(; ρ0=2650kg / m^3, α=3e-5 / K, T0=0.0C, β=β_rock / Pa), + Density = ConstantDensity(; ρ=2650kg / m^3), + # Density = PT_Density(; ρ0=2650kg / m^3, α=3e-5 / K, T0=0.0C, β=β_rock / Pa), HeatCapacity = ConstantHeatCapacity(; Cp=1050J / kg / K), Conductivity = ConstantConductivity(; k=3.0Watt / K / m), LatentHeat = ConstantLatentHeat(; Q_L=350e3J / kg), @@ -146,11 +162,12 @@ function init_rheology(CharDim; is_compressible = false) Elasticity = el, CharDim = CharDim, ), - + #Name="Magma" SetMaterialParams(; Phase = 2, - Density = PT_Density(; ρ0=2650kg / m^3, T0=0.0C, β=β_magma / Pa), + # Density = PT_Density(; ρ0=2650kg / m^3, T0=0.0C, β=β_magma / Pa), + Density = ConstantDensity(; ρ=2625kg / m^3), HeatCapacity = ConstantHeatCapacity(; Cp=1050J / kg / K), Conductivity = ConstantConductivity(; k=1.5Watt / K / m), LatentHeat = ConstantLatentHeat(; Q_L=350e3J / kg), @@ -202,7 +219,7 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals # Physical Parameters rheology = init_rheology(CharDim; is_compressible=true) cutoff_visc = nondimensionalize((1e16Pa*s, 1e24Pa*s),CharDim) - κ = (4 / (rheology[1].HeatCapacity[1].Cp * rheology[1].Density[1].ρ0)) + κ = (4 / (rheology[1].HeatCapacity[1].Cp * rheology[1].Density[1].ρ)) dt = dt_diff = (0.5 * min(di...)^2 / κ / 2.01) # diffusive CFL timestep limiter # Initialize particles ------------------------------- @@ -221,11 +238,11 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals x_anomaly = lx * 0.5 y_anomaly = ly * 0.5 z_anomaly = -lz * 0.5 # origin of the small thermal anomaly - r_anomaly = nondimensionalize(5km, CharDim) # radius of perturbation + r_anomaly = nondimensionalize(10km, CharDim) # radius of perturbation anomaly = nondimensionalize(50K, CharDim) # thermal perturbation (in K) phase_ratios = PhaseRatio(backend_JR, ni, length(rheology)) init_phases!(pPhases, particles, x_anomaly, y_anomaly, z_anomaly, r_anomaly, sticky_air, nondimensionalize(0.0km,CharDim), lz) - phase_ratios_center(phase_ratios, particles, grid, pPhases) + phase_ratios_center!(phase_ratios, particles, grid, pPhases) # Initialisation of thermal profile thermal = ThermalArrays(backend_JR, ni) # initialise thermal arrays and boundary conditions @@ -244,7 +261,7 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals circular_perturbation!( thermal.T, anomaly, x_anomaly, y_anomaly, z_anomaly, r_anomaly, xvi, sticky_air ) - thermal_bcs!(thermal.T, thermal_bc) + thermal_bcs!(thermal, thermal_bc) temperature2center!(thermal) # STOKES --------------------------------------------- @@ -262,17 +279,16 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals update_halo!(@velocity(stokes)...) # Buoyancy force & viscosity - args = (; T=thermal.Tc, P=stokes.P, dt=Inf) - ρg = @zeros(ni...), @zeros(ni...), @zeros(ni...) # ρg[1] is the buoyancy force in the x direction, ρg[2] is the buoyancy force in the y direction - for _ in 1:5 - compute_ρg!(ρg[end], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) - @parallel init_P!(stokes.P, ρg[3], xci[3], sticky_air) - end + args = (; T=thermal.Tc, P=stokes.P, dt=Inf) + ρbg = nondimensionalize(2650kg / m^3, CharDim) + ρg = @zeros(ni...), @zeros(ni...), @zeros(ni...) # ρg[1] is the buoyancy force in the x direction, ρg[2] is the buoyancy force in the y direction + compute_ρg!(ρg[end], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) compute_viscosity!(stokes, phase_ratios, args, rheology, cutoff_visc) pt_thermal = PTThermalCoeffs( - backend_JR, rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=0.8 / √3.1 + backend_JR, rheology, phase_ratios, args, dt, ni, di, li; ϵ=1e-5, CFL=1e-2 / √3.1 ) + Plitho = reverse(cumsum(reverse((ρg[2]).* di[2], dims=2), dims=2), dims=2) # Arguments for functions @copy thermal.Told thermal.T @@ -286,6 +302,26 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals take(figdir) # ---------------------------------------------------- + # Smooth out thermal field --------------------------- + for _ in 1:1 + heatdiffusion_PT!( + thermal, + pt_thermal, + thermal_bc, + rheology, + args, + nondimensionalize(50e3 * 3600 * 24 * 365.25 * s, CharDim), + di; + kwargs =(; + igg = igg, + phase = phase_ratios, + iterMax = 150e3, + nout = 1e3, + verbose = true, + ) + ) + end + # Plot initial T and η profiles # let # Zv = [z for _ in xvi[1], _ in xvi[2], z in xvi[3]][:] @@ -300,7 +336,8 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals # ) # scatter!( # ax2, - # Array(ustrip.(dimensionalize(stokes.P[:], MPa, CharDim))), + # # Array(ustrip.(dimensionalize(stokes.P[:], MPa, CharDim))), + # Array(ρg[end][:]), # ustrip.(dimensionalize(Z, km, CharDim)), # ) # hideydecorations!(ax2) @@ -322,15 +359,20 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals @copy stokes.P0 stokes.P @copy thermal.Told thermal.T - Tsurf = thermal.T[1, 1, end] - Tbot = thermal.T[1, 1, 1] + Tsurf, Tbot = extrema(thermal.T) + # Tbot = thermal.T[1, 1, 1] while it < 25 # # Update buoyancy and viscosity - # args = (; T=thermal.Tc, P=stokes.P, dt=Inf) - # compute_ρg!(ρg[end], phase_ratios, rheology, (T=thermal.Tc, P=stokes.P)) - # compute_viscosity!(stokes, phase_ratios, args, rheology, cutoff_visc) + # Update buoyancy and viscosity - + Plitho .= reverse(cumsum(reverse((ρg[3]).* di[2], dims=3), dims=3), dims=3) + Plitho .= stokes.P .+ Plitho .- minimum(stokes.P) + + args = (; T = thermal.Tc, P = Plitho, dt=Inf, ρbg = ρbg * rheology[1].Gravity[1].g) + compute_ρg!(ρg[end], phase_ratios, rheology, args) + compute_viscosity!(stokes, phase_ratios, args, rheology, cutoff_visc) # Stokes solver ----------------- solve!( @@ -390,12 +432,12 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals # check if we need to inject particles inject_particles_phase!(particles, pPhases, (pT, ), (thermal.T,), xvi) # update phase ratios - phase_ratios_center(phase_ratios, particles, grid, pPhases) + phase_ratios_center!(phase_ratios, particles, grid, pPhases) particle2grid!(thermal.T, pT, xvi, particles) @views thermal.T[:, :, end] .= Tsurf @views thermal.T[:, :, 1] .= Tbot - thermal_bcs!(thermal.T, thermal_bc) + thermal_bcs!(thermal, thermal_bc) temperature2center!(thermal) thermal.ΔT .= thermal.T .- thermal.Told vertex2center!(thermal.ΔTc, thermal.ΔT) @@ -431,12 +473,18 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals εII = Array(ustrip.(dimensionalize(stokes.ε.II, s^-1,CharDim))), η = Array(ustrip.(dimensionalize(η,Pa*s,CharDim))), ) + velocity_v = ( + Array(Vx_v), + Array(Vy_v), + Array(Vz_v), + ) save_vtk( joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), xvi, xci, data_v, data_c, + velocity_v ) end @@ -464,9 +512,10 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals return nothing end -figdir = "Thermal_stresses_around_cooling_magma_3D" +figdir = "/scratch/snx3000/ademonts/Blober" +figdir = "Blober" do_vtk = true # set to true to generate VTK files for ParaView -n = 16 +n = 32 nx = n ny = n nz = n diff --git a/docs/src/man/subduction2d/setup.md b/docs/src/man/subduction2d/setup.md index 2247b93c..35f65db2 100644 --- a/docs/src/man/subduction2d/setup.md +++ b/docs/src/man/subduction2d/setup.md @@ -16,14 +16,14 @@ Phases = zeros(Int64, nx, 1, nz); Temp = fill(Tbot, nx, 1, nz); ``` -In this model we have four material phases with their own phase numbers: +In this model we have four material phases with their respective phase numbers: | Material | Phase number | | :---------------- | :----------: | | asthenosphere | 0 | | oceanic lithosphere | 1 | | oceanic crust | 3 | -| air | 4 | +| sticky air | 4 | We will start by initializing the model as asthenospheric mantle, with a thermal profile given by the half-space cooling model with an age of 80 Myrs. diff --git a/subduction/Buiter/Buiter2D.jl b/subduction/Buiter/Buiter2D.jl index 409b9ea8..7e6ce148 100644 --- a/subduction/Buiter/Buiter2D.jl +++ b/subduction/Buiter/Buiter2D.jl @@ -1,5 +1,5 @@ -# const isCUDA = false -const isCUDA = true +const isCUDA = false +# const isCUDA = true @static if isCUDA using CUDA @@ -424,3 +424,16 @@ else end main(li, origin, phases_GMG, igg; figdir = figdir, nx = nx, ny = ny, do_vtk = do_vtk); + +const DeviceTrait = @static if ENV["JULIA_JUSTRELAX_BACKEND"] === "AMDGPU" + AMDGPUBackendTrait +elseif ENV["JULIA_JUSTRELAX_BACKEND"] === "CUDA" + CUDABackendTrait +else + CPUBackendTrait +end + +const DeviceTrait = CPUBackendTrait + +@test bk(Array) === AMDGPUBackendTrait() + \ No newline at end of file diff --git a/subduction/Buiter/Buiter_rheology.jl b/subduction/Buiter/Buiter_rheology.jl index e03d3260..2b8f269c 100644 --- a/subduction/Buiter/Buiter_rheology.jl +++ b/subduction/Buiter/Buiter_rheology.jl @@ -92,10 +92,10 @@ function init_rheologies(lithosphere_rheology; ρbg = 0e0) # Name = "StickyAir", SetMaterialParams(; Phase = 4, - Density = ConstantDensity(; ρ=1-ρbg), # water density - HeatCapacity = ConstantHeatCapacity(; Cp = Cp), - Conductivity = ConstantConductivity(; k = 2.5), - CompositeRheology = CompositeRheology((LinearViscous(; η=1e20),)), + Density = ConstantDensity(; ρ=100-ρbg), # water density + HeatCapacity = ConstantHeatCapacity(; Cp=3e3), + Conductivity = ConstantConductivity(; k=1.0), + CompositeRheology = CompositeRheology((LinearViscous(; η=1e19),)), ), ) end From 9eafb2074bbb1abf4a6cb09198f9460ab918a529 Mon Sep 17 00:00:00 2001 From: Albert de Montserrat Date: Fri, 24 May 2024 17:13:53 +0200 Subject: [PATCH 59/60] update 3D stokes --- src/stokes/Stokes3D.jl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/stokes/Stokes3D.jl b/src/stokes/Stokes3D.jl index 07c99ae3..507516da 100644 --- a/src/stokes/Stokes3D.jl +++ b/src/stokes/Stokes3D.jl @@ -207,6 +207,10 @@ function _solve!( compute_ρg!(ρg[end], phase_ratios, rheology, args) compute_viscosity!(stokes, phase_ratios, args, rheology, viscosity_cutoff) + (; ρbg) = args + ρgz_diff = ρg[3] .- ρbg + Plitho = reverse(cumsum(reverse((ρg[2]) .* di[2]; dims=3); dims=3); dims=3) + # solver loop wtime0 = 0.0 while iter < 2 || (err > ϵ && iter ≤ iterMax) @@ -226,12 +230,15 @@ function _solve!( pt_stokes.r, pt_stokes.θ_dτ, ) + args.P .= stokes.P .+ Plitho .- minimum(stokes.P) + @parallel (@idx ni) compute_strain_rate!( stokes.∇V, @strain(stokes)..., @velocity(stokes)..., _di... ) # Update buoyancy - update_ρg!(ρg[3], rheology, args) + update_ρg!(ρg[end], rheology, args) + @. ρgz_diff = ρg[end] - ρbg update_viscosity!( stokes, @@ -275,7 +282,9 @@ function _solve!( stokes.R.Ry, stokes.R.Rz, stokes.P, - ρg..., + ρg[1], + ρg[2], + ρgz_diff, @stress(stokes)..., ητ, pt_stokes.ηdτ, @@ -283,7 +292,7 @@ function _solve!( ) # apply boundary conditions flow_bcs!(stokes, flow_bcs) - update_halo!(stokes.V.Vx, stokes.V.Vy, stokes.V.Vz) + update_halo!(@velocity(stokes)...) end end From c9b298c426cf188764e98d3bfe33edf96701407f Mon Sep 17 00:00:00 2001 From: Albert de Montserrat <58044444+albert-de-montserrat@users.noreply.github.com> Date: Wed, 29 May 2024 10:01:20 +0200 Subject: [PATCH 60/60] Update Blober.jl --- Blober.jl | 78 +++++++++++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/Blober.jl b/Blober.jl index f9dae2b8..5753639d 100644 --- a/Blober.jl +++ b/Blober.jl @@ -1,5 +1,5 @@ -const isCUDA = false -# const isCUDA = true +# const isCUDA = false +const isCUDA = true @static if isCUDA using CUDA @@ -32,22 +32,10 @@ else JustPIC.CPUBackend # Options: CPUBackend, CUDABackend, AMDGPUBackend end -using GeoParams, GLMakie, CellArrays +using GeoParams, CairoMakie, CellArrays ## SET OF HELPER FUNCTIONS PARTICULAR FOR THIS SCRIPT -------------------------------- -# import ParallelStencil.INDICES -# const idx_j = INDICES[3] -# macro all_j(A) -# return esc(:($A[$idx_j])) -# end - -# @parallel function init_P!(P, ρg, z, sticky_air) -# @all(P) = @all(ρg) -# # @all(P) = abs(@all(ρg) * (@all_j(z) + sticky_air)) * <((@all_j(z) + sticky_air), 0.0) -# return nothing -# end - function init_phases!(phases, particles, xc_anomaly, yc_anomaly, zc_anomaly, r_anomaly, sticky_air,top, bottom) ni = size(phases) @@ -150,8 +138,8 @@ function init_rheology(CharDim; is_compressible = false) #Name="UpperCrust" SetMaterialParams(; Phase = 1, - Density = ConstantDensity(; ρ=2650kg / m^3), - # Density = PT_Density(; ρ0=2650kg / m^3, α=3e-5 / K, T0=0.0C, β=β_rock / Pa), + # Density = ConstantDensity(; ρ=2650kg / m^3), + Density = PT_Density(; ρ0=2650kg / m^3, α=3e-5 / K, T0=0.0C, β=β_rock / Pa), HeatCapacity = ConstantHeatCapacity(; Cp=1050J / kg / K), Conductivity = ConstantConductivity(; k=3.0Watt / K / m), LatentHeat = ConstantLatentHeat(; Q_L=350e3J / kg), @@ -166,8 +154,8 @@ function init_rheology(CharDim; is_compressible = false) #Name="Magma" SetMaterialParams(; Phase = 2, - # Density = PT_Density(; ρ0=2650kg / m^3, T0=0.0C, β=β_magma / Pa), - Density = ConstantDensity(; ρ=2625kg / m^3), + Density = PT_Density(; ρ0=2650kg / m^3, T0=0.0C, β=β_magma / Pa), + # Density = ConstantDensity(; ρ=2625kg / m^3), HeatCapacity = ConstantHeatCapacity(; Cp=1050J / kg / K), Conductivity = ConstantConductivity(; k=1.5Watt / K / m), LatentHeat = ConstantLatentHeat(; Q_L=350e3J / kg), @@ -446,21 +434,19 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals t += dt # # # Plotting ------------------------------------------------------- - if it == 1 || rem(it, 1) == 0 + if it == 1 || rem(it, 10) == 0 (; η) = stokes.viscosity # checkpointing(figdir, stokes, thermal.T, η, t) if igg.me == 0 if do_vtk velocity2vertex!(Vx_v, Vy_v, Vz_v, @velocity(stokes)...) + data_v = (; T = Array(ustrip.(dimensionalize(thermal.T, C, CharDim))), τxy= Array(ustrip.(dimensionalize(stokes.τ.xy, s^-1, CharDim))), εxy= Array(ustrip.(dimensionalize(stokes.ε.xy, s^-1, CharDim))), - Vx = Array(ustrip.(dimensionalize(Vx_v,cm/yr,CharDim))), - Vy = Array(ustrip.(dimensionalize(Vy_v, cm/yr, CharDim))), - Vz = Array(ustrip.(dimensionalize(Vz_v, cm/yr, CharDim))), - ) + ) data_c = (; P = Array(ustrip.(dimensionalize(stokes.P,MPa,CharDim))), τxx = Array(ustrip.(dimensionalize(stokes.τ.xx, MPa,CharDim))), @@ -474,9 +460,9 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals η = Array(ustrip.(dimensionalize(η,Pa*s,CharDim))), ) velocity_v = ( - Array(Vx_v), - Array(Vy_v), - Array(Vz_v), + Array(ustrip.(dimensionalize(Vx_v,cm/yr,CharDim))), + Array(ustrip.(dimensionalize(Vy_v, cm/yr, CharDim))), + Array(ustrip.(dimensionalize(Vz_v, cm/yr, CharDim))), ) save_vtk( joinpath(vtk_dir, "vtk_" * lpad("$it", 6, "0")), @@ -488,22 +474,22 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals ) end - let - Zv = [z for _ in 1:nx+1, _ in 1:ny+1, z in ustrip.(dimensionalize(xvi[3],km,CharDim))][:] - Z = [z for _ in 1:nx , _ in 1:ny , z in ustrip.(dimensionalize(xci[3],km,CharDim))][:] - fig = Figure(; size=(1200, 900)) - ax1 = Axis(fig[1, 1]; aspect = 2 / 3, title="T") - ax2 = Axis(fig[1, 2]; aspect = 2 / 3, title="Pressure") - a3 = Axis(fig[2, 1]; aspect = 2 / 3, title="τII") - - scatter!(ax1, ustrip.(dimensionalize((Array(thermal.T)), C, CharDim))[:] , Zv, markersize = 4) - scatter!(ax2, ustrip.(dimensionalize((Array(stokes.P)), MPa, CharDim))[:] , Z , markersize = 4) - scatter!(a3, ustrip.(dimensionalize(Array(stokes.τ.II), MPa, CharDim))[:], Z , markersize = 4) - - hideydecorations!(ax2) - save(joinpath(figdir, "pressure_profile_$it.png"), fig) - fig - end + # let + # Zv = [z for _ in 1:nx+1, _ in 1:ny+1, z in ustrip.(dimensionalize(xvi[3],km,CharDim))][:] + # Z = [z for _ in 1:nx , _ in 1:ny , z in ustrip.(dimensionalize(xci[3],km,CharDim))][:] + # fig = Figure(; size=(1200, 900)) + # ax1 = Axis(fig[1, 1]; aspect = 2 / 3, title="T") + # ax2 = Axis(fig[1, 2]; aspect = 2 / 3, title="Pressure") + # a3 = Axis(fig[2, 1]; aspect = 2 / 3, title="τII") + + # scatter!(ax1, ustrip.(dimensionalize((Array(thermal.T)), C, CharDim))[:] , Zv, markersize = 4) + # scatter!(ax2, ustrip.(dimensionalize((Array(stokes.P)), MPa, CharDim))[:] , Z , markersize = 4) + # scatter!(a3, ustrip.(dimensionalize(Array(stokes.τ.II), MPa, CharDim))[:], Z , markersize = 4) + + # hideydecorations!(ax2) + # save(joinpath(figdir, "pressure_profile_$it.png"), fig) + # fig + # end end end end @@ -513,9 +499,9 @@ function main3D(igg; figdir = "output", nx = 64, ny = 64, nz = 64, do_vtk = fals end figdir = "/scratch/snx3000/ademonts/Blober" -figdir = "Blober" +figdir = "Blober100" do_vtk = true # set to true to generate VTK files for ParaView -n = 32 +n = 100 nx = n ny = n nz = n @@ -526,4 +512,4 @@ else end # run main script -# main3D(igg; figdir=figdir, nx=nx, ny=ny, nz=nz, do_vtk = do_vtk); +main3D(igg; figdir=figdir, nx=nx, ny=ny, nz=nz, do_vtk = do_vtk);

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 Ibbp|4RHm$>6SMLVrp9bUN_!?Y+8MWN_bP6~ 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 HbL0O5L;x8E literal 0 HcmV?d00001 diff --git a/docs/src/man/subduction2d/setup_5.png b/docs/src/man/subduction2d/setup_5.png new file mode 100644 index 0000000000000000000000000000000000000000..6910d24b48258ed1a910fc2da8d1600154be14a6 GIT binary patch literal 75108 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