Skip to content

Commit 74760eb

Browse files
authored
Merge pull request #153 from adbayonao/Andres_Bayona
Implement segmented ridges with flexible positioning
2 parents d9ee88a + 6284a62 commit 74760eb

File tree

6 files changed

+206
-30
lines changed

6 files changed

+206
-30
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ tutorial/test.tiff
2020
*.vtr
2121
*.vts
2222
*.jld2
23+
*.swm
24+
*.swn
25+
*.swo
2326
*.tiff
2427
*.nc
2528
!/test/test_files/ISCTest.xml

ext/GMT_utils.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,13 @@ function import_GeoTIFF(fname::String; fieldname = :layer1, negative = false, is
158158
if length(size(G.image)) == 3
159159
data = permutedims(G.image, [2, 1, 3])
160160
elseif length(size(G.image)) == 2
161-
data[:, :, 1] = G.image'
161+
if size(G.image)==(nx,ny)
162+
data[:,:,1] = G.image
163+
elseif size(G.image)==(ny,nx)
164+
data[:,:,1] = G.image'
165+
else
166+
error("unknown size; ")
167+
end
162168
end
163169

164170
end

src/Setup_geometry.jl

Lines changed: 152 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ end
170170
Origin=nothing, StrikeAngle=0, DipAngle=0,
171171
phase = ConstantPhase(1),
172172
T=nothing,
173+
segments=nothing,
173174
cell=false )
174175
175176
Adds a box with phase & temperature structure to a 3D model setup. This simplifies creating model geometries in geodynamic models
@@ -188,6 +189,7 @@ Parameters
188189
- `DipAngle` - dip angle of slab
189190
- `phase` - specifies the phase of the box. See `ConstantPhase()`,`LithosphericPhases()`
190191
- `T` - specifies the temperature of the box. See `ConstantTemp()`,`LinearTemp()`,`HalfspaceCoolingTemp()`,`SpreadingRateTemp()`,`LithosphericTemp()`
192+
- `segments` - optional parameter to define multiple ridge segments within the box
191193
- `cell` - if true, `Phase` and `Temp` are defined on centers
192194
193195
Examples
@@ -223,15 +225,32 @@ julia> write_paraview(Grid,"LaMEM_ModelSetup") # Save model to paraview
223225
1-element Vector{String}:
224226
"LaMEM_ModelSetup.vts"
225227
```
228+
229+
Example 3) Box with ridge thermal structure
230+
```julia-repl
231+
julia> Grid = CartData(xyz_grid(-1000:10:1000, -1000:10:1000, -660:5:0))
232+
julia> Phases = fill(2, size(Grid));
233+
julia> Temp = fill(1350.0, size(Grid));
234+
julia> segments = [((-500.0, -1000.0), (-500.0, 0.0)),
235+
((-250.0, 0.0), (-250.0, 200.0)),
236+
((-750.0, 200.0), (-750.0, 1000.0))];
237+
julia> lith = LithosphericPhases(Layers=[15 55], Phases=[1 2], Tlab=1250);
238+
julia> add_box!(Phases, Temp, Grid; xlim=(-1000.0, 0.0), ylim=(-500.0, 500.0),
239+
zlim=(-80.0, 0.0), phase=lith,
240+
T=SpreadingRateTemp(SpreadingVel=3), segments=segments)
241+
julia> Grid = addfield(Grid, (; Phases, Temp)); # Add to Cartesian model
242+
julia> write_paraview(Grid, "Ridge_Thermal_Structure") # Save model to Paraview
243+
1-element Vector{String}:
244+
"Ridge_Thermal_Structure.vts"
226245
"""
227-
function add_box!(
228-
Phase, Temp, Grid::AbstractGeneralGrid; # required input
229-
xlim::Tuple = (20, 100), ylim = nothing, zlim::Tuple = (10, 80), # limits of the box
230-
Origin = nothing, StrikeAngle = 0, DipAngle = 0, # origin & dip/strike
246+
function add_box!(Phase, Temp, Grid::AbstractGeneralGrid; # required input
247+
xlim::Tuple = (20,100), ylim=nothing, zlim::Tuple = (10,80), # limits of the box
248+
Origin=nothing, StrikeAngle=0, DipAngle=0, # origin & dip/strike
231249
phase = ConstantPhase(1), # Sets the phase number(s) in the box
232-
T = nothing, # Sets the thermal structure (various functions are available)
233-
cell = false
234-
) # if true, Phase and Temp are defined on cell centers
250+
T=nothing, # Sets the thermal structure (various functions are available)
251+
segments=nothing, # Allows defining multiple ridge segments
252+
cell=false ) # if true, Phase and Temp are defined on cell centers
253+
235254

236255
# Retrieve 3D data arrays for the grid
237256
X, Y, Z = coordinate_grids(Grid, cell = cell)
@@ -279,17 +298,19 @@ function add_box!(
279298
if isa(T, LithosphericTemp)
280299
Phase[ind_flat] = compute_phase(Phase[ind_flat], Temp[ind_flat], Xrot[ind], Yrot[ind], Zrot[ind], phase)
281300
end
282-
Temp[ind_flat] = compute_thermal_structure(Temp[ind_flat], Xrot[ind], Yrot[ind], Zrot[ind], Phase[ind_flat], T)
301+
if segments !== nothing
302+
Temp[ind_flat] = compute_thermal_structure(Temp[ind_flat], X[ind], Y[ind], Z[ind], Phase[ind_flat], T, segments)
303+
else
304+
Temp[ind_flat] = compute_thermal_structure(Temp[ind_flat], Xrot[ind], Yrot[ind], Zrot[ind], Phase[ind_flat], T)
305+
end
283306
end
284-
285307
# Set the phase. Different routines are available for that - see below.
286308
Phase[ind_flat] = compute_phase(Phase[ind_flat], Temp[ind_flat], Xrot[ind], Yrot[ind], Zrot[ind], phase)
287309
end
288310

289311
return nothing
290312
end
291313

292-
293314
"""
294315
add_layer!(Phase, Temp, Grid::AbstractGeneralGrid; xlim::Tuple = (1,100), [ylim::Tuple = (0,20)], zlim::Tuple = (0,-100),
295316
phase = ConstantPhase(1),
@@ -1126,31 +1147,32 @@ Note: the thermal age at the mid oceanic ridge is set to 1 year to avoid divisio
11261147
Adiabat = 0 # Adiabatic gradient in K/km
11271148
MORside = "left" # side of box where the MOR is located
11281149
SpreadingVel = 3 # spreading velocity [cm/yr]
1129-
AgeRidge = 0 # Age of the ridge [Myrs]
1130-
maxAge = 60 # maximum thermal age of plate [Myrs]
1150+
AgeRidge = 0 # Age of the ridge [Myrs
1151+
maxAge = 60 # maximum thermal age of plate [Myrs]
11311152
end
11321153

11331154
function compute_thermal_structure(Temp, X, Y, Z, Phase, s::SpreadingRateTemp)
1134-
@unpack Tsurface, Tmantle, Adiabat, MORside, SpreadingVel, AgeRidge, maxAge = s
1135-
1136-
kappa = 1.0e-6
1137-
SecYear = 3600 * 24 * 365
1138-
dz = Z[end] - Z[1]
1139-
1140-
MantleAdiabaticT = Tmantle .+ Adiabat * abs.(Z) # Adiabatic temperature of mantle
1155+
@unpack Tsurface, Tmantle, Adiabat, MORside, SpreadingVel, AgeRidge, maxAge = s
1156+
1157+
kappa = 1e-6;
1158+
SecYear = 3600*24*365
1159+
dz = Z[end]-Z[1];
1160+
1161+
MantleAdiabaticT = Tmantle .+ Adiabat*abs.(Z); # Adiabatic temperature of mantle
1162+
1163+
if MORside=="left"
1164+
Distance = X .- X[1,1,1];
1165+
elseif MORside=="right"
1166+
Distance = X[end,1,1] .- X;
1167+
elseif MORside=="front"
1168+
Distance = Y .- Y[1,1,1];
1169+
elseif MORside=="back"
1170+
Distance = Y[1,end,1] .- Y;
11411171

1142-
if MORside == "left"
1143-
Distance = X .- X[1, 1, 1]
1144-
elseif MORside == "right"
1145-
Distance = X[end, 1, 1] .- X
1146-
elseif MORside == "front"
1147-
Distance = Y .- Y[1, 1, 1]
1148-
elseif MORside == "back"
1149-
Distance = Y[1, end, 1] .- Y
11501172
else
11511173
error("unknown side")
11521174
end
1153-
1175+
11541176
for i in eachindex(Temp)
11551177
ThermalAge = abs(Distance[i] * 1.0e3 * 1.0e2) / SpreadingVel + AgeRidge * 1.0e6 # Thermal age in years
11561178
if ThermalAge > maxAge * 1.0e6
@@ -1163,10 +1185,112 @@ function compute_thermal_structure(Temp, X, Y, Z, Phase, s::SpreadingRateTemp)
11631185
end
11641186

11651187
Temp[i] = (Tsurface .- Tmantle) * erfc((abs.(Z[i]) * 1.0e3) ./ (2 * sqrt(kappa * ThermalAge))) + MantleAdiabaticT[i]
1188+
11661189
end
1190+
11671191
return Temp
11681192
end
11691193

1194+
"""
1195+
SpreadingRateTemp(Temp, X, Y, Z, Phase, s::SpreadingRateTemp, segments)
1196+
1197+
Calculates the temperature distribution across the plate considering multiple ridge segments.
1198+
1199+
This function computes the thermal structure based on the perpendicular distance from each point to its corresponding ridge segment, and applies a thermal model using a spreading velocity and thermal age.
1200+
1201+
Parameters
1202+
==========
1203+
- Temp : Temperature field to be updated (array)
1204+
- X, Y, Z : Coordinates of the points (arrays)
1205+
- Phase : Phase of the material (unused in this version)
1206+
- s : SpreadingRateTemp object containing the thermal and spreading parameters
1207+
- segments : List of ridge segments, where each segment is defined by two tuples representing the start and end coordinates (x1, y1) and (x2, y2) for each segment.
1208+
1209+
Note
1210+
====
1211+
The temperature at each point is calculated using the thermal age, which is determined by the distance from the point to the nearest ridge segment and the spreading velocity.
1212+
1213+
The function works in the context of one or more segments. The key difference from the previous function is that the ridge can now be placed at any position within the box, not just at the boundary.
1214+
1215+
The thermal age is capped at `maxAge` years, and the temperature is adjusted based on the distance to the ridge and the corresponding thermal gradient.
1216+
1217+
"""
1218+
1219+
function compute_thermal_structure(Temp, X, Y, Z, Phase, s::SpreadingRateTemp, segments::Vector{Tuple{Tuple{Float64, Float64}, Tuple{Float64, Float64}}})
1220+
@unpack Tsurface, Tmantle, Adiabat, SpreadingVel, AgeRidge, maxAge = s
1221+
kappa = 1e-6;
1222+
SecYear = 3600 * 24 * 365
1223+
dz = Z[end]-Z[1];
1224+
1225+
MantleAdiabaticT = Tmantle .+ Adiabat * abs.(Z)
1226+
1227+
#Create delimiters
1228+
delimiters = [(segments[i][2], segments[i + 1][1]) for i in 1:length(segments) - 1]
1229+
1230+
for I in eachindex(X)
1231+
px, py, pz = X[I], Y[I], Z[I]
1232+
1233+
# Determine region of point
1234+
region = determine_region(px, py, delimiters, segments)
1235+
1236+
# Select the corresponding segment
1237+
x1, y1 = segments[region][1]
1238+
x2, y2 = segments[region][2]
1239+
1240+
# Calculate distance to segment
1241+
Distance = perpendicular_distance_to_segment(px, py, x1, y1, x2, y2)
1242+
1243+
# Calculate thermal age
1244+
ThermalAge = abs(Distance * 1e5) / SpreadingVel + AgeRidge * 1e6 # Thermal age in years
1245+
if ThermalAge > maxAge * 1e6
1246+
ThermalAge = maxAge * 1e6
1247+
end
1248+
1249+
ThermalAge = ThermalAge * SecYear # Convert to seconds
1250+
if ThermalAge == 0
1251+
ThermalAge = 1e-6 # Avoid zero
1252+
end
1253+
1254+
# Calculate temperature
1255+
Temp[I] = (Tsurface - Tmantle) * erfc(abs(pz) * 1e3 / (2 * sqrt(kappa * ThermalAge))) + MantleAdiabaticT[I]
1256+
end
1257+
1258+
return Temp
1259+
1260+
end
1261+
1262+
# Supporting functions for multi-segment ridge functionality
1263+
1264+
# Function to calculate the perpendicular distance from a point to a segment
1265+
function perpendicular_distance_to_segment(x, y, x1, y1, x2, y2)
1266+
num = abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1)
1267+
den = sqrt((y2 - y1)^2 + (x2 - x1)^2)
1268+
return num / den
1269+
end
1270+
1271+
# Function to determine the side of a point with respect to a line (adjusted for segment direction)
1272+
function side_of_line(x, y, x1, y1, x2, y2, direction)
1273+
side = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)
1274+
direction == :left ? side > 0 : side < 0
1275+
end
1276+
1277+
# Function to determine in which region a point lies (based on delimiters)
1278+
function determine_region(px, py, delimiters, segments)
1279+
for i in 1:length(delimiters)
1280+
x1, y1 = delimiters[i][1]
1281+
x2, y2 = delimiters[i][2]
1282+
1283+
# Determine the direction of the segments
1284+
direction = x2 < x1 ? :left : :right
1285+
1286+
# Check the side of the line considering the direction
1287+
if side_of_line(px, py, x1, y1, x2, y2, direction)
1288+
return i # Region corresponding to segment i
1289+
end
1290+
end
1291+
return length(segments) # Last region
1292+
end
1293+
11701294
"""
11711295
LithosphericTemp(Tsurface=0.0, Tpot=1350.0, dTadi=0.5,
11721296
ubound="const", lbound="const, utbf = 50.0e-3, ltbf = 10.0e-3,

test/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ using Test
7474
include("test_sea_level.jl")
7575
end
7676

77+
@testset "Ridge Thermal Structure Tests" begin
78+
include("test_ridge_segments.jl")
79+
end
80+
7781
@testset "Waterflow" begin
7882
include("test_WaterFlow.jl")
7983
end

test/test_GMT.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ test_fwd = import_GeoTIFF("test_files/length_fwd.tif", fieldname = :forward)
1111
@test maximum(test_fwd.fields.forward) 33.17775km
1212

1313
test2 = import_GeoTIFF("test_files/UTM2GTIF.TIF")
14-
@test test2.fields.layer1[20, 20] == 105.0
14+
@test test2.fields.layer1[20, 20] == 233.0

test/test_ridge_segments.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using GeophysicalModelGenerator
2+
using Test
3+
4+
@testset "Ridge Thermal Structure Tests" begin
5+
# Grid parameters
6+
nx, ny, nz = 512, 512, 128
7+
x = range(-1000, 1000, length=nx)
8+
y = range(-1000, 1000, length=ny)
9+
z = range(-660, 0, length=nz)
10+
Grid = CartData(xyz_grid(x, y, z))
11+
12+
# Phases and temperature
13+
Phases = fill(2, nx, ny, nz)
14+
Temp = fill(1350.0, nx, ny, nz)
15+
16+
# Ridge Segments
17+
segments = [
18+
((-500.0, -1000.0), (-500.0, 0.0)), # Segment 1
19+
((-250.0, 0.0), (-250.0, 200.0)), # Segment 2
20+
((-750.0, 200.0), (-750.0, 1000.0)) # Segment 3
21+
]
22+
23+
# Lithospheric phases
24+
lith = LithosphericPhases(Layers=[15 55], Phases=[1 2], Tlab=1250)
25+
add_box!(Phases, Temp, Grid; xlim=(-1000.0,0.0), ylim=(-500.0, 500.0), zlim=(-80.0, 0.0), phase = lith, T=SpreadingRateTemp(SpreadingVel=3), segments=segments)
26+
27+
# Add and save results
28+
Grid = addfield(Grid, (; Phases, Temp))
29+
#write_paraview(Grid, "Ridge_Thermal_Structure_test_2")
30+
31+
@test minimum(Temp) >= 0.0 # Minimum temperature
32+
@test maximum(Temp) <= 1350.0 # Maximum temperature
33+
@test all((0), Temp) # Ensure no negative temperatures
34+
@test all((1350), Temp) # Ensure no temperatures above max
35+
36+
# Check if phases are correctly assigned in expected regions
37+
@test first(Phases) == 2 # Example: Verify a point's phase
38+
@test last(Phases) == 2 # Example: Verify another point's phase
39+
end

0 commit comments

Comments
 (0)