World Builder  1.1.0-pre
A geodynamic initial conditions generator
plume.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 
21 
22 
28 #include "world_builder/nan.h"
35 #include "world_builder/world.h"
36 
37 #include <iostream>
38 #include <algorithm>
39 
40 
41 namespace WorldBuilder
42 {
43  using namespace Utilities;
44 
45  namespace Features
46  {
47  Plume::Plume(WorldBuilder::World *world_)
48  :
49  min_depth(NaN::DSNAN),
50  max_depth(NaN::DSNAN)
51  {
52  this->world = world_;
53  this->name = "plume";
54  }
55 
57  = default;
58 
59 
60 
62  {
63  using namespace rapidjson;
64  Document &declarations = prm.declarations;
65 
66  const std::string path = prm.get_full_json_path();
67 
68  Pointer((path + "/body").c_str()).Set(declarations,"object");
69  Pointer((path + "/body/model").c_str()).Set(declarations,"plume");
70  Pointer((path + "/body/name").c_str()).Set(declarations,"${1:My Plume}");
71  Pointer((path + "/body/coordinates").c_str()).Create(declarations).SetArray();
72  Pointer((path + "/body/temperature models").c_str()).Create(declarations).SetArray();
73  Pointer((path + "/body/composition models").c_str()).Create(declarations).SetArray();
74  }
75 
76 
77 
78  void
80  const std::string & /*unused*/,
81  const std::vector<std::string> &required_entries)
82  {
83  prm.declare_entry("", Types::Object(required_entries), "Plume object. Requires properties `model` and `coordinates`.");
84 
85  prm.declare_entry("min depth", Types::Double(0),
86  "The depth from which this feature is present, in other words, the "
87  "depth of the tip of the plume. If the first entry in the cross "
88  "section depths has a greater depth, an ellipsoidal plume head will "
89  "be added in between. Units: m.");
90  prm.declare_entry("max depth", Types::Double(std::numeric_limits<double>::max()),
91  "The depth to which this feature is present. Units: m.");
92  prm.declare_entry("cross section depths", Types::Array(Types::Double(0)),
93  "The depths of the elliptic cross section of the plume. Units: m.");
94  prm.declare_entry("semi-major axis", Types::Array(Types::Double(100.e3)),
95  "The lengths of the semi-major axes of the elliptic cross sections of the plume. "
96  "In spherical coordinates, this is in degrees, otherwise in meters.");
97  prm.declare_entry("eccentricity", Types::Array(Types::Double(0)),
98  "The eccentricities of the cross sections.");
99  prm.declare_entry("rotation angles", Types::Array(Types::Double(0)),
100  "The directions that the semi-major axis of the elliptic cross-sections "
101  "are pointing to, in degrees. This direction is expressed as the angle from "
102  "geographic North in spherical coordinates, or as the angle from the Y axis "
103  "(clockwise) in Cartesian coordinates. "
104  "The angle should be between 0 and 360 degrees.");
105 
106  prm.declare_entry("temperature models",
108  "A list of temperature models.");
109  prm.declare_entry("composition models",
111  "A list of composition models.");
112  prm.declare_entry("grains models",
114  "A list of grains models.");
115  prm.declare_entry("velocity models",
117  "A list of velocity models.");
118  }
119 
120  void
122  {
123  const CoordinateSystem coordinate_system = prm.coordinate_system->natural_coordinate_system();
124 
125  this->name = prm.get<std::string>("name");
126 
127  std::string tag = prm.get<std::string>("tag");
128  if (tag == "")
129  {
130  tag = "plume";
131  }
133 
134  this->get_coordinates("coordinates", prm, coordinate_system);
135 
136  min_depth = prm.get<double>("min depth");
137  max_depth = prm.get<double>("max depth");
138 
139  depths = prm.get_vector<double>("cross section depths");
140  semi_major_axis_lengths = prm.get_vector<double>("semi-major axis");
141  eccentricities = prm.get_vector<double>("eccentricity");
142  rotation_angles = prm.get_vector<double>("rotation angles");
143 
144  for (unsigned int i = 0; i < depths.size()-1; ++i)
145  WBAssert(depths[i] < depths[i+1],
146  "The depths of the elliptic cross sections of the plume need to be listed in ascending order.");
147 
148  WBAssert(depths.size() == coordinates.size(),
149  "The cross section depths array needs to have the same number of entries as there are coordinates. At the moment there are: "
150  << depths.size()
151  << " depth entries but "
152  << coordinates.size()
153  << " coordinates!");
154 
155  WBAssert(semi_major_axis_lengths.size() == coordinates.size(),
156  "The semi-major axis array needs to have the same number of entries as there are coordinates. At the moment there are: "
157  << semi_major_axis_lengths.size()
158  << " semi-major axis entries but "
159  << coordinates.size()
160  << " coordinates!");
161 
162  WBAssert(eccentricities.size() == coordinates.size(),
163  "The eccentricity array needs to have the same number of entries as there are coordinates. At the moment there are: "
164  << eccentricities.size()
165  << " eccentricity entries but "
166  << coordinates.size()
167  << " coordinates!");
168 
169  WBAssert(rotation_angles.size() == coordinates.size(),
170  "The rotation angles array needs to have the same number of entries as there are coordinates. At the moment there are: "
171  << rotation_angles.size()
172  << " rotation angle entries but "
173  << coordinates.size()
174  << " coordinates!");
175 
176 
177  // Convert degrees to radians, convert from geographical to mathematical
178  for (double &rotation_angle : rotation_angles)
179  rotation_angle = Consts::PI/2. - rotation_angle * Consts::PI/180.;
180 
181  // convert semi_major_axis_lengths to radians if we are in spherical coordinates
182  if (world->parameters.coordinate_system->natural_coordinate_system() == CoordinateSystem::spherical)
183  for (double &semi_major_axis_length : semi_major_axis_lengths)
184  {
185  semi_major_axis_length *= Consts::PI/180.;
186  }
187 
189 
190  prm.enter_subsection("temperature models");
191  {
192  for (unsigned int i = 0; i < temperature_models.size(); ++i)
193  {
194  prm.enter_subsection(std::to_string(i));
195  {
196  temperature_models[i]->parse_entries(prm);
197  }
198  prm.leave_subsection();
199  }
200  }
201  prm.leave_subsection();
202 
203 
205 
206  prm.enter_subsection("composition models");
207  {
208  for (unsigned int i = 0; i < composition_models.size(); ++i)
209  {
210  prm.enter_subsection(std::to_string(i));
211  {
212  composition_models[i]->parse_entries(prm);
213  }
214  prm.leave_subsection();
215  }
216  }
217  prm.leave_subsection();
218 
219 
221 
222  prm.enter_subsection("grains models");
223  {
224  for (unsigned int i = 0; i < grains_models.size(); ++i)
225  {
226  prm.enter_subsection(std::to_string(i));
227  {
228  grains_models[i]->parse_entries(prm);
229  }
230  prm.leave_subsection();
231  }
232  }
233  prm.leave_subsection();
234 
236 
237  prm.enter_subsection("velocity models");
238  {
239  for (unsigned int i = 0; i < velocity_models.size(); ++i)
240  {
241  prm.enter_subsection(std::to_string(i));
242  {
243  velocity_models[i]->parse_entries(prm);
244  }
245  prm.leave_subsection();
246  }
247  }
248  prm.leave_subsection();
249  }
250 
251 
252 
253  void
254  Plume::properties(const Point<3> &position_in_cartesian_coordinates,
255  const Objects::NaturalCoordinate &position_in_natural_coordinates,
256  const double depth,
257  const std::vector<std::array<unsigned int,3>> &properties,
258  const double gravity_norm,
259  const std::vector<size_t> &entry_in_output,
260  std::vector<double> &output) const
261  {
262  // Figure out if the point is within the plume
263  auto upper = std::upper_bound(depths.begin(), depths.end(), depth);
264 
265  Point<2> plume_center(coordinates[0]);
266  double semi_major_axis_length;
267  double eccentricity;
268  double rotation_angle;
269 
270  if (depth < min_depth)
271  return;
272  else if (upper - depths.begin() == 0)
273  {
274  // interpolate to make the top of the plume spherical
275  plume_center = coordinates.front();
276  eccentricity = eccentricities.front();
277  rotation_angle = rotation_angles.front();
278 
279  const double fraction = (depth - min_depth) / (depths.front() - min_depth);
280 
281  const double a = depths.front() - min_depth;
282  const double b = semi_major_axis_lengths.front();
283  const double y = (1. - fraction) * a;
284 
285  // use ellipse equation:
286  semi_major_axis_length = std::sqrt((1 - std::pow(y/a,2)) * b*b);
287  }
288  else if (upper - depths.end() == 0)
289  {
290  plume_center = coordinates.back();
291  semi_major_axis_length = semi_major_axis_lengths.back();
292  eccentricity = eccentricities.back();
293  rotation_angle = rotation_angles.back();
294  }
295  else
296  {
297  const unsigned int index = static_cast<unsigned int>(std::distance(depths.begin(), upper));
298  const double fraction = (depth - depths[index-1]) / (depths[index] - depths[index-1]);
299 
300  plume_center[0] = (1-fraction) * coordinates[index-1][0] + fraction * (coordinates[index][0]);
301  plume_center[1] = (1-fraction) * coordinates[index-1][1] + fraction * (coordinates[index][1]);
302 
303  semi_major_axis_length = (1-fraction) * semi_major_axis_lengths[index-1] + fraction * semi_major_axis_lengths[index];
304  eccentricity = (1-fraction) * eccentricities[index-1] + fraction * eccentricities[index];
305 
306  // For the angles, we only want to go between zero and pi, and we have to make sure we
307  // interpolate the values close to zero/pi correctly:
308  rotation_angle = interpolate_angle_across_zero(rotation_angles[index-1], rotation_angles[index], fraction);
309  }
310 
311  const Point<2> surface_point(position_in_natural_coordinates.get_surface_coordinates(),
312  world->parameters.coordinate_system->natural_coordinate_system());
313 
314  double relative_distance_from_center =
316  semi_major_axis_length,
317  eccentricity,
318  rotation_angle,
319  surface_point);
320 
321  // If we are in the tip, we have to compute the difference diffently:
322  if (depth >= min_depth && depth < depths.front())
323  {
324  const double a = semi_major_axis_lengths.front();
325  const double b = a * std::sqrt(1 - std::pow(eccentricity, 2));
326  const double c = depths.front() - min_depth;
327 
328  const double x = (surface_point[0] - plume_center[0]) * std::cos(rotation_angle) + (surface_point[1] - plume_center[1])* std::sin(rotation_angle);
329  const double y = -(surface_point[0] - plume_center[0]) * std::sin(rotation_angle) + (surface_point[1] - plume_center[1])* std::cos(rotation_angle);
330  const double z = depths.front() - depth;
331 
332  // use ellipsoid equation:
333  relative_distance_from_center = (x*x)/(a*a) + (y*y)/(b*b) + (z*z)/(c*c);
334  }
335 
336  if (depth <= max_depth && depth >= min_depth && relative_distance_from_center <= 1.)
337  {
338 
339  for (unsigned int i_property = 0; i_property < properties.size(); ++i_property)
340  {
341  switch (properties[i_property][0])
342  {
343  case 1: // temperature
344  {
345  for (const auto &temperature_model: temperature_models)
346  {
347  output[entry_in_output[i_property]] = temperature_model->get_temperature(position_in_cartesian_coordinates,
348  position_in_natural_coordinates,
349  depth,
350  gravity_norm,
351  output[entry_in_output[i_property]],
352  min_depth,
353  max_depth,
354  relative_distance_from_center);
355 
356  WBAssert(!std::isnan(output[entry_in_output[i_property]]), "Temperature is not a number: " << output[entry_in_output[i_property]]
357  << ", based on a temperature model with the name " << temperature_model->get_name() << ", in feature " << this->name);
358  WBAssert(std::isfinite(output[entry_in_output[i_property]]), "Temperature is not finite: " << output[entry_in_output[i_property]]
359  << ", based on a temperature model with the name " << temperature_model->get_name() << ", in feature " << this->name);
360 
361  }
362  break;
363  case 2: // composition
364 
365  for (const auto &composition_model: composition_models)
366  {
367  output[entry_in_output[i_property]] = composition_model->get_composition(position_in_cartesian_coordinates,
368  position_in_natural_coordinates,
369  depth,
370  properties[i_property][1],
371  output[entry_in_output[i_property]],
372  min_depth,
373  max_depth);
374 
375  WBAssert(!std::isnan(output[entry_in_output[i_property]]), "Composition is not a number: " << output[entry_in_output[i_property]]
376  << ", based on a composition model with the name " << composition_model->get_name() << ", in feature " << this->name);
377  WBAssert(std::isfinite(output[entry_in_output[i_property]]), "Composition is not finite: " << output[entry_in_output[i_property]]
378  << ", based on a composition model with the name " << composition_model->get_name() << ", in feature " << this->name);
379 
380  }
381 
382  break;
383  }
384  case 3: // grains
385  {
386  WorldBuilder::grains grains(output,properties[i_property][2],entry_in_output[i_property]);
387  for (const auto &grains_model: grains_models)
388  {
389  grains = grains_model->get_grains(position_in_cartesian_coordinates,
390  position_in_natural_coordinates,
391  depth,
392  properties[i_property][1],
393  grains,
394  min_depth,
395  max_depth);
396 
397  }
398  grains.unroll_into(output,entry_in_output[i_property]);
399  break;
400  }
401  case 4:
402  {
403  output[entry_in_output[i_property]] = static_cast<double>(tag_index);
404  break;
405  }
406  case 5: // velocity
407  {
408  std::array<double, 3> velocity = {{0,0,0}};
409  for (const auto &velocity_model: velocity_models)
410  {
411  velocity = velocity_model->get_velocity(position_in_cartesian_coordinates,
412  position_in_natural_coordinates,
413  depth,
414  gravity_norm,
415  velocity,
416  min_depth,
417  max_depth,
418  relative_distance_from_center);
419 
420  //WBAssert(!std::isnan(output[entry_in_output[i_property]]), "Velocity is not a number: " << output[entry_in_output[i_property]]
421  // << ", based on a velocity model with the name " << velocity_model->get_name() << ", in feature " << this->name);
422  //WBAssert(std::isfinite(output[entry_in_output[i_property]]), "Velocity is not a finite: " << output[entry_in_output[i_property]]
423  // << ", based on a velocity model with the name " << velocity_model->get_name() << ", in feature " << this->name);
424  }
425 
426  output[entry_in_output[i_property]] = velocity[0];
427  output[entry_in_output[i_property]+1] = velocity[1];
428  output[entry_in_output[i_property]+2] = velocity[2];
429  break;
430  }
431  default:
432  {
433  WBAssertThrow(false,
434  "Internal error: Unimplemented property provided. " <<
435  "Only temperature (1), composition (2), grains (3), tag (4) or velocity (5) are allowed. "
436  "Provided property number was: " << properties[i_property][0]);
437  }
438  }
439  }
440  }
441  }
442 
443  WB_REGISTER_FEATURE(Plume, plume)
444 
445  } // namespace Features
446 } // namespace WorldBuilder
std::vector< double > rotation_angles
Definition: plume.h:169
static void declare_entries(Parameters &prm, const std::string &parent_name, const std::vector< std::string > &required_entries)
Definition: interface.cc:40
std::vector< std::unique_ptr< Features::PlumeModels::Temperature::Interface > > temperature_models
Definition: plume.h:136
std::vector< double > semi_major_axis_lengths
Definition: plume.h:167
std::vector< std::unique_ptr< Features::PlumeModels::Velocity::Interface > > velocity_models
Definition: plume.h:152
std::array< double, 2 > get_surface_coordinates() const
const double DSNAN
Definition: nan.h:41
size_t add_vector_unique(std::vector< std::string > &vector, const std::string &add_string)
#define WB_REGISTER_FEATURE(classname, name)
Definition: interface.h:212
void enter_subsection(const std::string &name)
Definition: parameters.cc:1871
std::vector< std::unique_ptr< Features::PlumeModels::Composition::Interface > > composition_models
Definition: plume.h:144
static void declare_entries(Parameters &prm, const std::string &parent_name, const std::vector< std::string > &required_entries)
Definition: interface.cc:41
void parse_entries(Parameters &prm) override final
Definition: plume.cc:121
std::vector< std::unique_ptr< Features::PlumeModels::Grains::Interface > > grains_models
Definition: plume.h:160
void properties(const Point< 3 > &position_in_cartesian_coordinates, const Objects::NaturalCoordinate &position_in_natural_coordinates, const double depth, const std::vector< std::array< unsigned int, 3 >> &properties, const double gravity, const std::vector< size_t > &entry_in_output, std::vector< double > &output) const override final
Definition: plume.cc:254
#define WBAssert(condition, message)
Definition: assert.h:27
std::string get_full_json_path(size_t max_size=std::numeric_limits< size_t >::max()) const
Definition: parameters.cc:1933
void get_coordinates(const std::string &name, Parameters &prm, const CoordinateSystem coordinate_system)
Definition: interface.cc:140
std::vector< double > eccentricities
Definition: plume.h:168
double cos(const double angle)
Definition: point.h:97
static void make_snippet(Parameters &prm)
Definition: plume.cc:61
std::vector< std::string > feature_tags
Definition: world.h:308
WorldBuilder::World * world
Definition: interface.h:127
#define WBAssertThrow(condition, message)
Definition: assert.h:40
bool get_unique_pointers(const std::string &name, std::vector< std::unique_ptr< T > > &vector)
Definition: parameters.cc:1770
static void declare_entries(Parameters &prm, const std::string &parent_name, const std::vector< std::string > &required_entries)
Definition: interface.cc:41
std::unique_ptr< WorldBuilder::CoordinateSystems::Interface > coordinate_system
Definition: parameters.h:268
double fraction_from_ellipse_center(const Point< 2 > &ellipse_center, const double semi_major_axis, const double eccentricity, const double theta, const Point< 2 > &point)
Definition: utilities.cc:167
double sin(const double raw_angle)
Definition: point.h:81
static void declare_entries(Parameters &prm, const std::string &parent_name, const std::vector< std::string > &required_entries)
Definition: interface.cc:39
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
static void declare_entries(Parameters &prm, const std::string &parent_name="", const std::vector< std::string > &required_entries={})
Definition: plume.cc:79
Parameters parameters
Definition: world.h:253
std::vector< T > get_vector(const std::string &name)
T get(const std::string &name)
rapidjson::Document declarations
Definition: parameters.h:248
std::vector< double > depths
Definition: plume.h:166
void unroll_into(std::vector< double > &vector, const size_t start_entry=0) const
Definition: grains.cc:58
double interpolate_angle_across_zero(const double angle_1, const double angle_2, const double fraction)
Definition: utilities.cc:1095
std::vector< Point< 2 > > coordinates
Definition: interface.h:154