World Builder  1.1.0-pre
A geodynamic initial conditions generator
mass_conserving.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2018-2024 by the authors of the World Builder code.
3 
4  This file is part of the World Builder.
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published
8  by the Free Software Foundation, either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public License
17  along with this program. If not, see <https://www.gnu.org/licenses/>.
18 
19 
20  Note that the empirical model used to define how Tmin increases with depth
21  and how the position of Tmin shift with depth is expected to change somewhat
22  after better calibrating with further tests.
23 */
24 
27 #include "world_builder/nan.h"
37 #include "world_builder/world.h"
39 
40 namespace WorldBuilder
41 {
42  using namespace Utilities;
43 
44  namespace Features
45  {
46  namespace SubductingPlateModels
47  {
48  namespace Temperature
49  {
50  MassConserving::MassConserving(WorldBuilder::World *world_)
51  :
52  min_depth(NaN::DSNAN),
53  max_depth(NaN::DSNAN),
54  density(NaN::DSNAN),
55  mantle_coupling_depth(NaN::DSNAN),
56  forearc_cooling_factor(NaN::DSNAN),
57  thermal_conductivity(NaN::DSNAN),
58  thermal_expansion_coefficient(NaN::DSNAN),
59  specific_heat(NaN::DSNAN),
60  thermal_diffusivity(NaN::DSNAN),
61  potential_mantle_temperature(NaN::DSNAN),
62  surface_temperature(NaN::DSNAN),
63  taper_distance(NaN::DSNAN),
64  adiabatic_heating(true),
65  operation(Operations::REPLACE)
66  {
67  this->world = world_;
68  this->name = "mass conserving";
69  }
70 
72 
73  void
74  MassConserving::declare_entries(Parameters &prm, const std::string & /*unused*/)
75  {
76  // Document plugin and require entries if needed.
77  // Add `spreading velocity` to the required parameters.
78  prm.declare_entry("", Types::Object({"spreading velocity", "subducting velocity"}),
79  "Mass conserving temperature model. The temperature "
80  "model uses the heat content (proportional to to thermal mass anomaly) to "
81  "define a smooth temperature profile that conserves mass along the slab length. "
82  "An empirical model, using error functions for smooth transitions, is used to "
83  " define how the minimum temperature increases with depth and how the location of "
84  "the minimum temperature shifts into the slab interior. The slab is divided into top "
85  "and bottom parts, which meet at the location where the minimum temperature occurs in the slab. "
86  "For the bottom slab, the temperature is defined by a half-space cooling model. "
87  "For the top of the slab the temperature is defined by one side of a 1D infinite "
88  "space cooling model: this function was chosen to have a smoother temperature function across "
89  "the minimum temperature position. The age of the overriding plate is used so the slab temperature "
90  "at shallow depth smoothly transitions to the temperature of the overriding plate: "
91  "this is not perfect, and is affected by the value of \"top truncation\" parameter "
92  "subducting plate. Notes:"
93  "1) the parameter \"thickness\" for the subducting plate segments needs to be defined but is not used. "
94  "2) because we use a negative truncation for distance above the slab, it is recommended to use"
95  "depth method:begin at end segment, in the main part of the world-builder file."
96  "Other methods may lead to gpas in temperatures at the segment boundaries."
97  "3)the empirical model used to define how Tmin increases with depth "
98  "and how the position of Tmin shift with depth is expected to change somewhat "
99  "after better calibrating with further tests.");
100 
101  // Declare entries of this plugin
102  prm.declare_entry("min distance slab top", Types::Double(0),
103  "The distance in meters from the top surface of the slab over which the temperature is "
104  "determined by this feature. This parameter should be negative and should be 1.5-2 times "
105  "larger than the nominal slab thickness to allow the diffusion of cold "
106  "temperatures from in the slab into the mantle above the slab surface. "
107  "Also note that the top truncation value for the slab segment needs to have a value "
108  "of -1, otherwise the temperature above the slab will be cut off at a distance less than "
109  "the value set here.");
110 
111  prm.declare_entry("max distance slab top", Types::Double(std::numeric_limits<double>::max()),
112  "The distance in meters from the top surface of the slab over which the temperature is "
113  "determined by this feature. This parameter should be positive and approximately 2.5-3.0 times "
114  "larger than the nominal slab thickness to allow the diffusion of cold"
115  "temperatures from in the slab into the mantle below the slab surface."
116  "For example if the slab starts with cold temperatures over a 100 km wide region, this"
117  "parameters should be about 250 km.");
118 
119  prm.declare_entry("density", Types::Double(3300),
120  "The reference density of the subducting plate in $kg/m^3$");
121 
122  prm.declare_entry("spreading velocity", Types::OneOf(Types::Double(0.05),Types::Array(Types::ValueAtPoints(0.05, std::numeric_limits<uint64_t>::max()))),
123  "The velocity with which the ridge spreads and create the plate in meters per year. Default is 5 cm/yr");
124 
125  prm.declare_entry("subducting velocity", Types::OneOf(Types::Double(0.05), Types::Array(Types::Array(Types::Double(0.05), 1), 1)),
126  "The velocity with which the slab is subducting through time. Default is 5 cm/yr");
127 
128  prm.declare_entry("coupling depth", Types::Double(100e3),
129  "The depth at which the slab surface first comes in contact with the hot mantle wedge "
130  "in meters. Default is 100 km.");
131 
132  prm.declare_entry("forearc cooling factor", Types::Double(1.0),
133  "Increase the value to create thin (~2 km) cold thermal boundary layer above the slab."
134  "Any value greater than 1 does NOT meet the instantaneous conservation of mass, but does allow "
135  "one to account for the history of insulating the forearc from heating up to this point in time. "
136  "Note younger subducting lithosphere provides less insulation, while thicker, older slabs "
137  "provide more insulation. Values up to 10 to 30 have been tested and don't cause any other "
138  "extraneous effects. The larger th value the more you are not meeting the mass conserving criteria, "
139  "so you don't want to see this affecting the temperature beyond the coupling depth as it will "
140  "increase the mass of the slab and affect how it sinks. If you use higher values, you will start to "
141  "see that this creates a very thick cool layer above the entire slab - if you see this extending beyond "
142  "the coupling zone reduce the value. You should use a value of 1 first and then "
143  "only increase as little as possible to cool just the forearc region. "
144  "Please examine the output temperature carefully. ");
145 
146  prm.declare_entry("thermal conductivity", Types::Double(3.3),
147  "The thermal conductivity of the subducting plate material in $W m^{-1} K^{-1}$.");
148 
149  prm.declare_entry("thermal expansion coefficient", Types::Double(-1),
150  "The thermal expansivity of the subducting plate material in $K^{-1}$. If smaller than zero, the global value is used.");
151 
152  prm.declare_entry("specific heat", Types::Double(-1),
153  "The specific heat of the subducting plate material in $J kg^{-1} K^{-1}$. If smaller than zero, the global value is used.");
154 
155  prm.declare_entry("thermal diffusivity", Types::Double(-1),
156  "The thermal conductivity of the subducting plate material in $W m^{-1} K^{-1}$.");
157 
158  prm.declare_entry("adiabatic heating", Types::Bool(true),
159  "Whether adiabatic heating should be used for the slab.");
160 
161  prm.declare_entry("taper distance", Types::Double(100e3),
162  "Distance over which to taper the slab tip."
163  "tapers the initial heat content to zero and the minimum temperature to the background temperature.");
164 
165  prm.declare_entry("potential mantle temperature", Types::Double(-1),
166  "The potential temperature of the mantle at the surface in Kelvin. If smaller than zero, the global value is used.");
167 
168  prm.declare_entry("ridge coordinates", Types::Array(Types::Array(Types::Point<2>(), 2),1),
169  "An list of ridges. Each ridge is a lists of at least 2 2d points which "
170  "define the location of the ridge. You need to define at least one ridge."
171  "So the an example with two ridges is "
172  "[[[10,20],[20,30],[10,40]],[[50,10],[60,10]]].");
173 
174  prm.declare_entry("reference model name", Types::String("half space model"),
175  "The type of thermal model to use in the mass conserving model of slab temperature. "
176  "Options are half space model and plate model");
177 
178  prm.declare_entry("apply spline", Types::Bool(false),
179  "Whether a spline should be applied on the mass conserving model.");
180 
181  prm.declare_entry("number of points in spline", Types::UnsignedInt(5),
182  "The number of points in the spline");
183 
184  }
185 
186  void
188  {
189 
190  min_depth = prm.get<double>("min distance slab top");
191  max_depth = prm.get<double>("max distance slab top");
192  operation = string_operations_to_enum(prm.get<std::string>("operation"));
193 
194  density = prm.get<double>("density");
195  thermal_conductivity = prm.get<double>("thermal conductivity");
196  ridge_spreading_velocities = prm.get_value_at_array("spreading velocity");
197  subducting_velocities = prm.get_vector_or_double("subducting velocity");
198 
199  mantle_coupling_depth = prm.get<double>("coupling depth");
200  forearc_cooling_factor = prm.get<double>("forearc cooling factor");
201 
202  taper_distance = prm.get<double>("taper distance");
203 
204  thermal_expansion_coefficient = prm.get<double>("thermal expansion coefficient");
205  if (thermal_expansion_coefficient < 0)
206  thermal_expansion_coefficient = this->world->thermal_expansion_coefficient;
207 
208  specific_heat = prm.get<double>("specific heat");
209  if (specific_heat < 0)
211 
212  thermal_diffusivity = prm.get<double>("thermal diffusivity");
213  if (thermal_diffusivity < 0)
215 
216  adiabatic_heating = prm.get<bool>("adiabatic heating");
217 
219  ?
221  :
222  prm.get<double>("potential mantle temperature");
223 
225 
226  mid_oceanic_ridges = prm.get_vector<std::vector<Point<2>>>("ridge coordinates");
227  const double dtr = prm.coordinate_system->natural_coordinate_system() == spherical ? Consts::PI / 180.0 : 1.0;
228  for (auto &ridge_coordinates : mid_oceanic_ridges)
229  for (auto &ridge_coordinate : ridge_coordinates)
230  {
231  ridge_coordinate *= dtr;
232  }
233 
234  unsigned int ridge_point_index = 0;
235  for (const auto &mid_oceanic_ridge : mid_oceanic_ridges)
236  {
237  std::vector<double> ridge_spreading_velocities_for_ridge;
238  for (unsigned int index_y = 0; index_y < mid_oceanic_ridge.size(); index_y++)
239  {
240  if (ridge_spreading_velocities.second.size() == 1)
241  {
242  ridge_spreading_velocities_for_ridge.push_back(ridge_spreading_velocities.second[0]);
243  }
244  else
245  {
246  ridge_spreading_velocities_for_ridge.push_back(ridge_spreading_velocities.second[ridge_point_index]);
247  }
248  ridge_point_index += 1;
249  }
250  ridge_spreading_velocities_at_each_ridge_point.push_back(ridge_spreading_velocities_for_ridge);
251  }
252 
253  std::string reference_model_name_str = prm.get<std::string>("reference model name");
254  if (reference_model_name_str=="plate model")
256  else if (reference_model_name_str=="half space model")
258 
259  apply_spline = prm.get<bool>("apply spline");
260  spline_n_points = prm.get<unsigned int>("number of points in spline");
261 
262  if (subducting_velocities[0].size() > 1)
263  {
264  for (unsigned int ridge_index = 0; ridge_index < mid_oceanic_ridges.size(); ridge_index++)
265  {
266  WBAssertThrow(subducting_velocities.size() == mid_oceanic_ridges.size() &&
267  subducting_velocities[ridge_index].size() == mid_oceanic_ridges[ridge_index].size(),
268  "subducting velocity must have the same dimension as the ridge coordinates parameter.");
269  for (unsigned int point_index = 0; point_index < mid_oceanic_ridges[ridge_index].size(); point_index++)
270  {
271  WBAssertThrow(Utilities::approx(subducting_velocities[ridge_index][point_index],
272  ridge_spreading_velocities_at_each_ridge_point[ridge_index][point_index]),
273  "Currently, subducting velocity must equal the spreading velocity to satisfy conservation of mass. "
274  "This will be changed in the future to allow for the spreading center to move depending on the relative "
275  "difference between the spreading velocity and the subducting velocity, thereby conserving mass.");
276  }
277  }
278  }
279  }
280 
281  double
282  MassConserving::get_temperature(const Point<3> & /*position_in_cartesian_coordinates*/,
283  const double depth,
284  const double gravity_norm,
285  double temperature_,
286  const double /*feature_min_depth*/,
287  const double /*feature_max_depth*/,
288  const WorldBuilder::Utilities::PointDistanceFromCurvedPlanes &distance_from_planes,
289  const AdditionalParameters &additional_parameters) const
290  {
291 
292  const double seconds_in_year = 60.0 * 60.0 * 24.0 * 365.25; // sec/y
293 
294  const double distance_from_plane = distance_from_planes.distance_from_plane;
295 
296  if (distance_from_plane <= max_depth && distance_from_plane >= min_depth)
297  {
298 
299  const Point<3> trench_point = distance_from_planes.closest_trench_point;
300  const Objects::NaturalCoordinate trench_point_natural = Objects::NaturalCoordinate(trench_point,
302 
303  std::vector<double> ridge_parameters = Utilities::calculate_ridge_distance_and_spreading(mid_oceanic_ridges,
306  trench_point_natural,
309 
310  constexpr double km2m = 1.0e3; // 1000 m/km
311  constexpr double cm2m = 100; // 100 cm/m
312  constexpr double my = 1.0e6; // 1e6 y/my
313 
314  /* information about nearest point on the slab segment */
315  const double distance_along_plane = distance_from_planes.distance_along_plane;
316  const double depth_to_reference_surface = distance_from_planes.depth_reference_surface;
317  const double total_segment_length = additional_parameters.total_local_segment_length;
318  const double average_angle = distance_from_planes.average_angle;
319 
320  std::vector<double> slab_ages = calculate_effective_trench_and_plate_ages(ridge_parameters, distance_along_plane);
321 
322  const double spreading_velocity = ridge_parameters[0] * seconds_in_year; // m/yr
323  double subducting_velocity = ridge_parameters[2] * seconds_in_year; // m/yr
324 
325  const double age_at_trench = slab_ages[0];
326  const double plate_age_sec = age_at_trench * seconds_in_year; // y --> seconds
327  // 1. Determine initial heat content of the slab based on age of plate at trench
328  // This uses the integral of the half-space temperature profile
329  // The initial heat content is also decided from the type of thermal model to use in the
330  // mass conserving model
331  double initial_heat_content;
333  {
334  initial_heat_content = thermal_conductivity / thermal_diffusivity *
336  for (int i = 0; i< std::floor(plate_model_summation_number/2.0); ++i)
337  {
338  // because n < sommation_number + 1 and n = 2k + 1
339  // The "spreading_velocity" instead of "spreading_velocity_UI" is used for the last instance as "age_at_trench" has
340  // a unit of yr.
341  const double subducting_velocity_UI = subducting_velocity / seconds_in_year;
342  const double temp_heat_content = thermal_conductivity / thermal_diffusivity *
344  4 * max_depth / double(2*i + 1) / double(2*i + 1) / Consts::PI / Consts::PI *
345  exp((subducting_velocity_UI * max_depth / 2 / thermal_diffusivity -
346  std::sqrt(subducting_velocity_UI * subducting_velocity_UI * max_depth * max_depth / 4.0 / thermal_diffusivity / thermal_diffusivity +
347  double(2*i + 1) * double(2*i + 1) * Consts::PI * Consts::PI)) *
348  subducting_velocity * age_at_trench / max_depth);
349  initial_heat_content -= temp_heat_content;
350  }
351  }
352  else
353  {
355  std::sqrt(plate_age_sec / (thermal_diffusivity * Consts::PI));
356  }
357 
358  // Plate age increases with distance along the slab in the mantle
359  double effective_plate_age_sec = slab_ages[1] * seconds_in_year;
360 
361  // Need adiabatic temperature at position of grid point
362  const double background_temperature = adiabatic_heating ? potential_mantle_temperature *
363  std::exp(thermal_expansion_coefficient * gravity_norm * depth / specific_heat)
365 
366  WBAssert(!std::isnan(background_temperature), "Internal error: temp is not a number: " << background_temperature << ". In exponent: "
367  << std::exp(((thermal_expansion_coefficient * gravity_norm) / specific_heat) * depth)
368  << ", thermal_expansion_coefficient = " << thermal_expansion_coefficient << ", gravity_norm = " << gravity_norm
369  << ", specific_heat = " << specific_heat << ", depth = " << depth);
370 
371  const double adiabatic_gradient = adiabatic_heating ? background_temperature - potential_mantle_temperature : 0;
372 
373  // 2. Get Tmin and offset as a function of depth: these depend on spreading velocity and plate age_at_trench.
374  // shallow-dipping slabs will take longer to reach the same depth - this leads to larger effective age at a given depth
375  // causing these slabs to be broader than steeper dipping slabs
376  // These equations are empirical based on fitting the temperature profiles from dynamic subduction models.
377  // and published kinematic models for specific subduction zones.
378 
379  // increases Tmin slope for slower relative to slope for maximum spreading velocity
380  // will be between 0.1 (fast) and 0.35 (slow)
381  const double max_plate_vel = 20/cm2m; // e.g., 20 cm/yr -> 0.2 m/yr
382  const double vsubfact = std::min( std::max( 0.35 + ((0.1-0.35) / max_plate_vel) * subducting_velocity, 0.1), 0.35);
383 
384  // increases Tmin slope for younger plate relative to slope for old place
385  // will be between 0.1 (old) and 0.35 *(young)
386  const double max_plate_age = 100*my; // years
387  const double max_age_fact = 1.0;
388  const double agefact = std::min( std::max( max_age_fact + ((0.1-max_age_fact) / max_plate_age) * age_at_trench, 0.1), max_age_fact);
389 
390  // increases offset relative to slab surface for older plates
391  // will be between 0.1 (young) and 0.35 (old)
392  const double agefact2 = std::max( std::min( 0.1 + ((0.35-0.1) / max_plate_age) * age_at_trench, 0.35), 0.1);
393 
394  // ranges between 0.5 (old and fast) and 1.0 (young and slow)
395  const double subfact = 0.3 + vsubfact + agefact; // this has a minimum value of 0.5 for the erfc
396  // ranges between 0.5 (young and fast) and 1 (old and slow)
397  const double subfact2 = 0.3 + vsubfact + agefact2;
398 
399  // Minimum Temperature
400  const double min_Tcoup = 10; // 0.3*mantle_coupling_depth/km2m; // deg C: from 0.3deg/km * 100 km = 30 deg
401  const double max_Tcoup = 350; // deg C: from Cascadia subduction models by Gao and Wang, Gcubd 2017
402  const double Tcoup = min_Tcoup + (subfact - 0.5)*(max_Tcoup - min_Tcoup);
403 
404  const double min_Tmin660 = 300; //110; // based on syracruse models (with 0.5 deg/km adiabat; (265-30)-0.5*240)
405  const double max_Tmin660 = 900;
406  const double Tmin660 = min_Tmin660 + (subfact - 0.5)*(max_Tmin660 - min_Tmin660);
407 
408  // Temperature offset
409  const double min_offset_coup = 2*km2m; // values from syracuse and time-dep Citcom run
410  const double max_offset_coup = 10*km2m;
411  const double offset_coup = min_offset_coup + (subfact2)*(max_offset_coup - min_offset_coup);
412 
413  const double min_offset660 = 15*km2m; // values from time-dep Citcom run
414  const double max_offset660 = 25*km2m;
415  const double offset660 = min_offset660 + (subfact2)*(max_offset660 - min_offset660);
416 
417  // For tapering the slab tip temperature to the mantle temperature
418  const double start_taper_distance = total_segment_length - taper_distance;
419 
420  const double upper_mantle_lengthscale = 660e3 - mantle_coupling_depth; // m
421 
422 
423  const double taper_con = 0.8; // controls how close taper gets to end of segment
424  double theta = 0;
425  double min_temperature = 0;
426  double offset = 0;
427 
428  if (depth_to_reference_surface < mantle_coupling_depth)
429  {
430  // above coupling depth
431  theta = (mantle_coupling_depth - depth_to_reference_surface) / (subfact * mantle_coupling_depth); // must be scaled depth_coupling to match Tcoup ad Tsurface.
432  min_temperature = Tcoup * std::erfc(theta);
433  offset = offset_coup * std::erfc(theta);
434  }
435  else if (distance_along_plane >= start_taper_distance)
436  {
437  // beyond start taper distance to taper the slab tip
438 
439  const double depth_start_taper = depth_to_reference_surface - (distance_along_plane - start_taper_distance)
440  * std::sin(average_angle * Consts::PI / 180.0);
441  const double theta_start = (mantle_coupling_depth - depth_start_taper) / (subfact * upper_mantle_lengthscale);
442  const double Tmin_start_taper = Tcoup + Tmin660 * (std::erfc(theta_start)) - Tmin660;
443  //keep the offset location constant in the taper
444  const double offset_start_taper = offset_coup + (offset660) * std::erfc(theta_start) - offset660;
445 
446  // taper Tmin to the mantle temperature
447  theta = (distance_along_plane - start_taper_distance) / (taper_distance);
448  min_temperature = Tmin_start_taper + (potential_mantle_temperature - Tmin_start_taper) * (1-std::erfc(taper_con*theta));
449  offset = offset_start_taper + (2*max_offset660 - offset_start_taper) * (1-std::erfc(taper_con*theta));
450 
451  // Also taper the initial heat content and effective plate age
452  initial_heat_content = initial_heat_content * std::erfc(1.5*taper_con*theta);
453  effective_plate_age_sec = effective_plate_age_sec * std::erfc(1.5*taper_con*theta);
454  }
455 
456  else
457  {
458  theta = (mantle_coupling_depth - depth_to_reference_surface) / (subfact * upper_mantle_lengthscale);
459  min_temperature = Tcoup + Tmin660 * std::erfc(theta) - Tmin660;
460  offset = offset_coup + offset660 * std::erfc(theta) - offset660;
461  }
462 
463  min_temperature = min_temperature + adiabatic_gradient + surface_temperature;
464 
465  // Adjust distance for the offset of the minimum temperature from the top of the slab
466  const double adjusted_distance = distance_from_plane - offset;
467 
468  // Base value chosen to insure that even young slabs have some cooling of the top of the slab
469  // Use forearc_cooling_factor input variable to increase the amount of long-term cooling of the forearc.
470  const double max_top_heat_content = -1.0e9 * forearc_cooling_factor * background_temperature; // need to multiply by background temperature since it gets divided by something of this scale.
471 
472  double temperature = 0.0; // temperature (to be determined) at this location in the mesh
473 
474  if (min_temperature < background_temperature)
475  {
476 
477  // 3. Determine the heat content for side 1 (bottom) of the slab
478  // Comes from integrating the half-space cooling model or the plate model temperature
479  // The bottom heat content is also decided from the type of thermal model to use in the
480  // mass conserving model
481  double bottom_heat_content;
483  {
484  bottom_heat_content = thermal_conductivity / thermal_diffusivity *
485  (min_temperature - potential_mantle_temperature) * max_depth / 2.0;
486  for (int i = 0; i< std::floor(plate_model_summation_number/2.0); ++i)
487  {
488  // because n < sommation_number + 1 and n = 2k + 1
489  const double subducting_velocity_UI = subducting_velocity / seconds_in_year;
490  const double temp_heat_content = thermal_conductivity / thermal_diffusivity *
491  (min_temperature - potential_mantle_temperature) *
492  4 * max_depth / double(2*i + 1) / double(2*i + 1) / Consts::PI / Consts::PI *
493  exp((subducting_velocity_UI * max_depth / 2.0 / thermal_diffusivity -
494  std::sqrt(subducting_velocity_UI * subducting_velocity_UI * max_depth * max_depth / 4.0 / thermal_diffusivity / thermal_diffusivity +
495  double(2*i + 1) * double(2*i + 1) * Consts::PI * Consts::PI)) *
496  subducting_velocity_UI * effective_plate_age_sec / max_depth);
497  bottom_heat_content -= temp_heat_content;
498  }
499  }
500  else
501  {
502  bottom_heat_content = 2 * thermal_conductivity * (min_temperature - potential_mantle_temperature) *
503  std::sqrt(effective_plate_age_sec /(thermal_diffusivity * Consts::PI));
504  }
505 
506  // 4. The difference in heat content goes into the temperature above where Tmin occurs.
507  // Should not be a positive value
508  //double top_heat_content = initial_heat_content - bottom_heat_content;
509 
510  double top_heat_content = std::min(max_top_heat_content, (initial_heat_content - bottom_heat_content) );
511 
512  // Also need to taper the top_heat_content otherwise slab top will continue to thicken to the tip.
513  // Can't do this above because need min_temperature to get bottom_heat_content first
514  if (distance_along_plane > start_taper_distance)
515  {
516  top_heat_content = top_heat_content * std::erfc(taper_con*theta);
517  }
518 
519  double nondimensional_adjusted_distance = adjusted_distance / max_depth;
520 
521  if (apply_spline)
522  {
523  // A total number of (2 * spline_n_points + 1) points are picked,
524  // spline_n_points points on each side and one at the center.
525  // These points cover a range of (-1.0, 1.0) in adjusted_distance.
526  Utilities::interpolation monotone_cubic_spline;
527 
528  const double interval_spline_distance = 1.0 / spline_n_points;
529  std::vector<double> i_temperatures (2*(spline_n_points + 1), 0.0);
530 
531  for (size_t i = 0; i < 2 * spline_n_points + 1; ++i)
532  {
533  const double i_adjusted_distance = (static_cast<double>(i) * interval_spline_distance - 1.0) * max_depth;
534  const double i_temperature = get_temperature_analytic(top_heat_content, min_temperature, background_temperature, temperature_, spreading_velocity, effective_plate_age_sec, i_adjusted_distance);
535  i_temperatures[i] = i_temperature;
536  }
537 
538  monotone_cubic_spline.set_points(i_temperatures);
539 
540  const double index_distance = (nondimensional_adjusted_distance + 1.0) / interval_spline_distance;
541  temperature = monotone_cubic_spline(index_distance);
542  }
543  else
544  {
545  // Call the analytic solution to compute the temperature
546  temperature = get_temperature_analytic(top_heat_content, min_temperature, background_temperature, temperature_, spreading_velocity, effective_plate_age_sec, adjusted_distance);
547  }
548  }
549  else
550  {
551  // slab temperature anomaly is gone.
552  temperature = temperature_;
553  }
554 
555  WBAssert(!std::isnan(temperature), "Internal error: temperature is not a number: " << temperature << '.');
556  WBAssert(std::isfinite(temperature), "Internal error: temperature is not finite: " << temperature << '.');
557 
558  return apply_operation(operation, temperature_, temperature);
559  }
560 
561  return temperature_;
562  }
563 
564  double
565  MassConserving::get_temperature_analytic(const double top_heat_content,
566  const double min_temperature,
567  const double background_temperature,
568  const double temperature_,
569  const double subducting_velocity,
570  const double effective_plate_age_sec,
571  const double adjusted_distance) const
572  {
573  const double seconds_in_year = 60.0 * 60.0 * 24.0 * 365.25; // sec/y
574 
575  double temperature = 0.0;
576 
577  // Assign the temperature depending on whether distance is negative (above) or positive (below) the slab
578  if (adjusted_distance < 0)
579  {
580  // use 1D infinite space solution for top (side 2) of slab the slab
581  // 2 times the "top_heat_content" because all this heat needs to be on one side of the Gaussian
582  const double time_top_slab = (1/(Consts::PI*thermal_diffusivity)) *
583  pow(((2 * top_heat_content) / (2 * density * specific_heat * (min_temperature - temperature_ + 1e-16))),2) + 1e-16;
584 
585  // for overriding plate region where plate temperature is less the minimum slab temperature
586  // need to set temperature = temperature_ otherwise end up with temperature less than surface temperature ;
587  if (temperature_ < min_temperature)
588  {
589  temperature = temperature_;
590  }
591  else
592  {
593  temperature = temperature_ + (2 * top_heat_content /
594  (2*density*specific_heat * std::sqrt(Consts::PI * thermal_diffusivity * time_top_slab)))*
595  std::exp(-(adjusted_distance*adjusted_distance)/(4*thermal_diffusivity*time_top_slab));
596  }
597  }
598  else
599  {
600  // use half-space cooling or plate model for the bottom (side 1) of the slab
602  {
603  if (adjusted_distance < max_depth)
604  {
605  const double subducting_velocity_UI = subducting_velocity / seconds_in_year;
606  temperature = background_temperature + (min_temperature - background_temperature) * (1 - adjusted_distance / max_depth);
607  for (int i = 1; i< std::floor(plate_model_summation_number/2.0); ++i)
608  {
609  temperature = temperature - (min_temperature - background_temperature) *
610  ((2 / (double(i) * Consts::PI)) * std::sin((double(i) * Consts::PI * adjusted_distance) / max_depth) *
611  std::exp((((subducting_velocity_UI * max_depth)/(2 * thermal_diffusivity)) -
612  std::sqrt(((subducting_velocity_UI*subducting_velocity_UI*max_depth*max_depth) /
613  (4*thermal_diffusivity*thermal_diffusivity)) + double(i) * double(i) * Consts::PI * Consts::PI)) *
614  ((subducting_velocity_UI * effective_plate_age_sec) / max_depth)));
615  }
616  }
617  else
618  {
619  temperature = background_temperature;
620  }
621  }
622  else
623  {
624  temperature = background_temperature + (min_temperature - background_temperature) *
625  std::erfc(adjusted_distance / (2 * std::sqrt(thermal_diffusivity * effective_plate_age_sec)));
626 
627  }
628  }
629  return temperature;
630  }
631 
633  } // namespace Temperature
634  } // namespace SubductingPlateModels
635  } // namespace Features
636 } // namespace WorldBuilder
static void declare_entries(Parameters &prm, const std::string &parent_name="")
const double DSNAN
Definition: nan.h:41
bool approx(double a, double b, double error_factor=1e4)
Definition: utilities.h:48
double potential_mantle_temperature
Definition: world.h:268
double get_temperature(const Point< 3 > &position, const double depth, const double gravity, double temperature, const double feature_min_depth, const double feature_max_depth, const WorldBuilder::Utilities::PointDistanceFromCurvedPlanes &distance_from_planes, const AdditionalParameters &additional_parameters) const override final
Operations string_operations_to_enum(const std::string &operation)
std::vector< double > calculate_ridge_distance_and_spreading(std::vector< std::vector< Point< 2 >>> mid_oceanic_ridges, std::vector< std::vector< double >> mid_oceanic_spreading_velocities, const std::unique_ptr< WorldBuilder::CoordinateSystems::Interface > &coordinate_system, const Objects::NaturalCoordinate &position_in_natural_coordinates_at_min_depth, const std::vector< std::vector< double >> &subducting_plate_velocities, const std::vector< double > &ridge_migration_times)
Definition: utilities.cc:1290
#define WBAssert(condition, message)
Definition: assert.h:27
void set_points(const std::vector< double > &y)
Definition: utilities.cc:1040
double thermal_diffusivity
Definition: world.h:293
std::pair< std::vector< double >, std::vector< double > > ridge_spreading_velocities
double specific_heat
Definition: world.h:288
double surface_temperature
Definition: world.h:273
#define WB_REGISTER_FEATURE_SUBDUCTING_PLATE_TEMPERATURE_MODEL(classname, name)
Definition: interface.h:156
#define WBAssertThrow(condition, message)
Definition: assert.h:40
std::unique_ptr< WorldBuilder::CoordinateSystems::Interface > coordinate_system
Definition: parameters.h:268
std::vector< double > calculate_effective_trench_and_plate_ages(std::vector< double > ridge_parameters, double distance_along_plane)
Definition: utilities.cc:1483
double thermal_expansion_coefficient
Definition: world.h:283
std::pair< std::vector< double >, std::vector< double > > get_value_at_array(const std::string &name)
Definition: parameters.cc:719
std::vector< std::vector< double > > get_vector_or_double(const std::string &name)
Definition: parameters.cc:920
double apply_operation(const Operations operation, const double old_value, const double new_value)
double sin(const double raw_angle)
Definition: point.h:81
double get_temperature_analytic(const double top_heat_content, const double min_temperature, const double background_temperature, const double temperature_, const double plate_velocity, const double effective_plate_age, const double adjusted_distance) const
constexpr double PI
Definition: consts.h:30
void declare_entry(const std::string &name, const Types::Interface &type, const std::string &documentation)
Definition: parameters.cc:197
Parameters parameters
Definition: world.h:253
std::vector< T > get_vector(const std::string &name)
T get(const std::string &name)