BubbleProfiler  0.3.0
by Peter Athron, Csaba Balazs, Michael Bardsley, Andrew Fowlie, Dylan Harries & Graham White
shooting_profile_guesser.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of BubbleProfiler.
3  *
4  * BubbleProfiler is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * BubbleProfiler is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with BubbleProfiler. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
19 #include "error.hpp"
20 #include "math_wrappers.hpp"
21 #include "potential.hpp"
22 #include "rotation.hpp"
23 
24 #include <sstream>
25 #include <vector>
26 
27 namespace BubbleProfiler {
28 
29 namespace {
30 
43 int find_strict_lower_bound(const Eigen::VectorXd& grid, double value)
44 {
45  if (value < grid(0)) {
46  return -1;
47  } else if (value > grid(grid.size() - 1)) {
48  return grid.size() - 1;
49  }
50 
51  int closest = 0;
52  grid.unaryExpr([value](double x) {
53  return Abs(x - value); }).minCoeff(&closest);
54 
55  return grid(closest) < value ? closest : closest - 1;
56 }
57 
70 int find_strict_upper_bound(const Eigen::VectorXd& grid, double value)
71 {
72  if (value < grid(0)) {
73  return 0;
74  } else if (value > grid(grid.size() - 1)) {
75  return -1;
76  }
77 
78  int closest = 0;
79  grid.unaryExpr([value](double x) {
80  return Abs(x - value); }).minCoeff(&closest);
81 
82  return grid(closest) > value ? closest : closest + 1;
83 }
84 
85 Eigen::VectorXd extend_domain(const Eigen::VectorXd& grid, double new_start,
86  double new_end, int new_length)
87 {
88  const int old_length = grid.size();
89 
90  if (old_length >= new_length) {
91  throw Setup_error("old grid must be shorter than new grid");
92  }
93 
94  const double old_start = grid(0);
95  const double old_end = grid(old_length - 1);
96 
97  if (new_start > old_start || new_end < old_end) {
98  throw Setup_error("new domain boundaries cannot be within old grid");
99  }
100 
101  const double extra_length = old_start - new_start + new_end - old_end;
102  const double ratio = (old_start - new_start) / extra_length;
103  const int n_extra = new_length - old_length;
104  const int n_front = static_cast<int>(std::floor(ratio * n_extra));
105  const int n_back = n_extra - n_front;
106 
107  Eigen::VectorXd new_grid(new_length);
108  new_grid.segment(n_front, old_length) = grid;
109 
110  if (n_front > 0) {
111  const double front_step = (old_start - new_start) / n_front;
112  for (int i = 0; i < n_front; ++i) {
113  new_grid(i) = new_start + i * front_step;
114  }
115  }
116 
117  if (n_back > 0) {
118  const double back_step = (new_end - old_end) / n_back;
119  for (int i = 0; i < n_back; ++i) {
120  new_grid(n_front + old_length + i) = old_end + i * back_step;
121  }
122  }
123 
124  return new_grid;
125 }
126 
127 template <typename InputIt>
128 auto get_subset(int num, InputIt first, InputIt last)
129  -> std::vector<typename std::iterator_traits<InputIt>::value_type>
130 {
131  using value_type = typename std::iterator_traits<InputIt>::value_type;
132 
133  if (num == 1) {
134  const auto halfway = std::distance(first, last) / 2;
135  return std::vector<value_type>{ *(first + halfway) };
136  } else if (num == 2) {
137  return std::vector<value_type>{ *first, *(last - 1) };
138  }
139 
140  const auto length = std::distance(first, last);
141  const auto midpoint = length / 2;
142 
143  std::vector<value_type> subset;
144  subset.reserve(num);
145 
146  if (num % 2 == 0) {
147  const auto next_length = num / 2;
148  const auto front
149  = get_subset(next_length, first, first + midpoint);
150  const auto back
151  = get_subset(next_length, first + midpoint, last);
152  subset.insert(std::end(subset), std::begin(front), std::end(front));
153  subset.insert(std::end(subset), std::begin(back), std::end(back));
154  } else {
155  const auto next_length = (num - 1) / 2;
156  const auto front
157  = get_subset(next_length, first, first + midpoint);
158  const auto back
159  = get_subset(next_length, first + midpoint + 1, last);
160 
161  subset.insert(std::end(subset), std::begin(front), std::end(front));
162  subset.push_back(*(first + midpoint));
163  subset.insert(std::end(subset), std::begin(back), std::end(back));
164  }
165 
166  return subset;
167 }
168 
169 Eigen::VectorXd injective_coarsening(const Eigen::VectorXd& grid,
170  int new_length)
171 {
172  const int old_length = grid.size();
173 
174  Eigen::VectorXd new_grid(new_length);
175  if (old_length % new_length == 0) {
176  const int stride = old_length / new_length;
177  for (int i = 0, j = 0; i < new_length; ++i, j += stride) {
178  new_grid(i) = grid(j);
179  }
180  } else {
181  std::vector<int> grid_indices(old_length);
182  std::iota(std::begin(grid_indices), std::end(grid_indices), 0);
183  const auto subset_indices = get_subset(new_length,
184  std::begin(grid_indices),
185  std::end(grid_indices));
186  for (int i = 0; i < new_length; ++i) {
187  new_grid(i) = grid(subset_indices[i]);
188  }
189  }
190 
191  return new_grid;
192 }
193 
194 Eigen::VectorXd coarsen_grid(const Eigen::VectorXd& grid, int new_length)
195 {
196  const int old_length = grid.size();
197 
198  if (old_length < new_length) {
199  throw Setup_error("old grid must be finer than new grid");
200  }
201 
202  return injective_coarsening(grid, new_length);
203 }
204 
205 } // anonymous namespace
206 
208  const Potential& potential, const Eigen::VectorXd& true_vacuum, int d,
209  double domain_start, double domain_end, double initial_step_size,
210  double interpolation_points_fraction)
211 {
212  compute_vacuum_distance(potential, true_vacuum);
213  calculate_potential_parameters(potential, true_vacuum);
214  return calculate_field_profiles(d, domain_start, domain_end,
215  initial_step_size,
216  interpolation_points_fraction);
217 }
218 
220  const Potential& potential, const Eigen::VectorXd& true_vacuum)
221 {
222  dist_true_vacuum = true_vacuum.norm();
223 
224  if (dist_true_vacuum == 0) {
225  throw Setup_error("Shooting_profile_guesser::get_profile_guess: "
226  "True and false vacua are coincident");
227  }
228 
229  num_fields = potential.get_number_of_fields();
230  Eigen::VectorXd origin(Eigen::VectorXd::Zero(num_fields));
231  if (Abs(potential(true_vacuum) - potential(origin)) < 1.e-12) {
232  throw Setup_error("Shooting_profile_guesser::get_profile_guess: "
233  "True and false vacua are degenerate");
234  }
235 }
236 
238  const Potential& potential, const Eigen::VectorXd& true_vacuum)
239 {
240  // Make a copy of the potential for use in ansatz construction
241  auto ansatz_potential = std::unique_ptr<Potential>(potential.clone());
242 
243  // Get change of basis matrix that orients first component
244  // in direction of true vacuum
245  cob_matrix = calculate_rotation_to_target(true_vacuum).transpose();
246 
247  std::stringstream log_str;
248  log_str << "COB: " << cob_matrix;
250 
251  // Apply the change of basis matrix. Note that we add
252  // the distance to the true vacuum as a scale factor so that the
253  // true vacuum is located at (1,0,0,...,0).
254  ansatz_potential->apply_basis_change(dist_true_vacuum * cob_matrix);
255 
256  // Add a constant term to enforce V(false_vacuum) = 0
257  ansatz_potential->add_constant_term(-1*ansatz_potential->operator()(
258  Eigen::VectorXd::Zero(num_fields)));
259 
260  // New coordinates of the true vacuum
261  Eigen::VectorXd _true_vacuum = Eigen::VectorXd::Zero(num_fields);
262  _true_vacuum(0) = 1;
263 
264  // Calculate ansatz parameters
266  + std::to_string(trial_dist));
267 
268  Eigen::VectorXd trial_vec = Eigen::VectorXd::Zero(num_fields);
269  trial_vec(0) = trial_dist;
270 
271  double vtrial = ansatz_potential->operator()(trial_vec);
272  double vmin = ansatz_potential->operator()(_true_vacuum);
273  double r = vmin / vtrial;
274 
275  E = 2. * ((2. - trial_dist * trial_dist) * vmin
276  - vtrial / (trial_dist * trial_dist))
277  / ((trial_dist - 1.) * (trial_dist - 1.));
278  alpha = (r * trial_dist * trial_dist * (trial_dist - 1.5) + 0.5)
279  / (1. - r * trial_dist * trial_dist * (2. - trial_dist * trial_dist));
280 
282  + std::to_string(alpha));
284  + std::to_string(Abs(E)));
285 }
286 
288 {
289  const auto potential = [this](double phi) {
290  return this->E * (
291  -this->alpha * phi * phi * phi * phi
292  + phi * phi * phi
293  + 0.5 * (4. * this->alpha - 3.) * phi * phi);
294  };
295 
296  const auto potential_first = [this](double phi) {
297  return this->E * (-4. * this->alpha * phi * phi * phi
298  + 3. * phi * phi
299  + (4. * this->alpha - 3.) * phi);
300  };
301 
302  const auto potential_second = [this](double phi) {
303  return this->E * (-12. * this->alpha * phi * phi
304  + 6. * phi + (4. * this->alpha - 3.));
305  };
306 
307  const double false_min = calculate_false_min_location();
308  const double barrier = calculate_barrier_location();
309  const double true_min = calculate_true_min_location();
310 
311  solver.solve(potential, potential_first, potential_second,
312  false_min, true_min, barrier, d,
313  Shooting::Solver_options::Compute_profile);
314 }
315 
317  int d, double domain_start, double domain_end,
318  double initial_step_size, double interpolation_points_fraction)
319 {
321 
322  Field_profiles initial_profile = solver.get_bubble_profile();
323  const auto n_solution_points = initial_profile.get_number_of_grid_points();
324  const double solution_start = initial_profile.get_domain_start();
325  const double solution_end = initial_profile.get_domain_end();
326 
327  int num_grid_points = 0;
328  if (domain_start < 0. && domain_end < 0.) {
329  num_grid_points = 1 + static_cast<int>(
330  std::ceil((solution_end - solution_start) / initial_step_size));
331  if (num_grid_points == n_solution_points) {
332  initial_profile.set_number_of_dimensions(d);
333  initial_profile.set_interpolation_points_fraction(
334  interpolation_points_fraction);
335  return initial_profile;
336  }
337  } else {
338  num_grid_points = 1 + static_cast<int>(
339  std::ceil((domain_end - domain_start) / initial_step_size));
340  }
341 
342  const auto initial_grid = initial_profile.get_spatial_grid();
343  const Eigen::VectorXd rho
344  = update_spatial_grid(initial_grid, domain_start,
345  domain_end, num_grid_points);
346 
347  Eigen::VectorXd ansatz(num_grid_points);
348  for (int i = 0; i < num_grid_points; ++i) {
349  const double r = rho(i);
350  if (r < solution_start) {
351  ansatz(i) = solver.quadratic_potential_solution(r);
352  } else if (r >= solution_start && r <= solution_end) {
353  ansatz(i) = initial_profile.evaluate_at(0, r);
354  } else {
355  ansatz(i) = get_large_distance_solution(r, initial_profile, d);
356  }
357  }
358 
359  // We still need to undo the change of basis
360  Eigen::MatrixXd cob_reverse = cob_matrix.transpose();
361 
362  // Note - we're building all the profiles here, then
363  // passing them over one at a time. This is inefficient,
364  // but (I think) the path of least resistance right now.
365  // Could be improved later.
366  Eigen::MatrixXd m_profiles(num_grid_points, num_fields);
367 
368  Eigen::VectorXd temp_field_vec = Eigen::VectorXd::Zero(num_fields);
369 
370  for (int x = 0; x < num_grid_points; ++x) {
371  // Ansatz basis field vec @ this radius
372  temp_field_vec(0) = ansatz(x);
373 
374  // Transform it to the original field basis (and undo the
375  // field coordinate scaling)
376  Eigen::VectorXd orig_field_vec
377  = cob_reverse * (dist_true_vacuum) * temp_field_vec;
378 
379  m_profiles.row(x) = orig_field_vec;
380  }
381 
382  Field_profiles profiles(rho, m_profiles, interpolation_points_fraction);
383  profiles.set_number_of_dimensions(d);
384 
385  return profiles;
386 }
387 
389  const Eigen::VectorXd& old_grid, double new_start, double new_end,
390  int new_length) const
391 {
392  const int old_length = old_grid.size();
393  const double old_start = old_grid(0);
394  const double old_end = old_grid(old_length - 1);
395 
396  double grid_start = new_start >= 0. ? new_start : old_start;
397  double grid_end = new_end >= 0. ? new_end : old_end;
398 
399  if (grid_start > grid_end) {
400  std::swap(grid_start, grid_end);
401  }
402 
403  const int start_index = find_strict_upper_bound(old_grid, grid_start);
404  const int end_index = find_strict_lower_bound(old_grid, grid_end);
405  if (start_index < 0 || end_index < 0) {
406  // either grid_start > largest value in old grid, or
407  // grid_end < smallest value, and new grid lies entirely
408  // outside old grid
409  return Eigen::VectorXd::LinSpaced(new_length, grid_start, grid_end);
410  }
411 
412  Eigen::VectorXd new_grid(new_length);
413  new_grid(0) = grid_start;
414  new_grid(new_length - 1) = grid_end;
415 
416  const int n_interior_points = end_index - start_index + 1;
417  const Eigen::VectorXd interior_points =
418  old_grid.segment(start_index, n_interior_points);
419 
420  if (n_interior_points == new_length - 2) {
421  new_grid.segment(1, new_length - 2) = interior_points;
422  } else if (n_interior_points < new_length - 2) {
423  new_grid.segment(1, new_length - 2) =
424  extend_domain(interior_points, grid_start, grid_end, new_length - 2);
425  } else {
426  new_grid.segment(1, new_length - 2) =
427  coarsen_grid(interior_points, new_length - 2);
428  }
429 
430  return new_grid;
431 }
432 
434 {
435  double local_min_loc;
436  if (E == 0.) {
437  local_min_loc = 0.;
438  } else if (E > 0.) {
439  if (alpha <= 0.375) {
440  local_min_loc = 1.;
441  } else if (alpha > 0.375 && alpha <= 0.75) {
442  local_min_loc = 0.25 * (3. - 4. * alpha) / alpha;
443  } else {
444  local_min_loc = 0.;
445  }
446  } else {
447  if (alpha <= 0.25) {
448  local_min_loc = 0.;
449  } else if (alpha > 0.25 && alpha <= 0.375) {
450  local_min_loc = 0.25 * (3. - 4. * alpha) / alpha;
451  } else if (alpha > 0.375 && alpha <= 0.5) {
452  local_min_loc = 1.;
453  } else if (alpha > 0.5 && alpha <= 0.75) {
454  local_min_loc = 0.;
455  } else {
456  local_min_loc = 0.25 * (3. - 4. * alpha) / alpha;
457  }
458  }
459  return local_min_loc;
460 }
461 
463 {
464  double local_max_loc;
465  if (E == 0.) {
466  local_max_loc = 0.;
467  } else if (E > 0.) {
468  if (alpha <= 0.25) {
469  local_max_loc = 0.;
470  } else if (alpha > 0.25 && alpha <= 0.375) {
471  local_max_loc = 0.25 * (3. - 4. * alpha) / alpha;
472  } else if (alpha > 0.375 && alpha <= 0.5) {
473  local_max_loc = 1.;
474  } else if (alpha > 0.5 && alpha <= 0.75) {
475  local_max_loc = 0.;
476  } else {
477  local_max_loc = 0.25 * (3. - 4. * alpha) / alpha;
478  }
479  } else {
480  if (alpha <= 0.375) {
481  // potential has one or two local maxima, or no barrier,
482  // returns the smaller barrier if multiple local maxima
483  local_max_loc = 1.;
484  } else if (alpha > 0.375 && alpha <= 0.75) {
485  local_max_loc = 0.25 * (3. - 4. * alpha) / alpha;
486  } else {
487  local_max_loc = 0.;
488  }
489  }
490  return local_max_loc;
491 }
492 
494 {
495  double global_min_loc;
496  if (E == 0.) {
497  global_min_loc = 0.;
498  } else if (E > 0.) {
499  if (alpha < 0.) {
500  global_min_loc = 0.25 * (3. - 4. * alpha) / alpha;
501  } else {
502  // potential is unbounded from below
503  throw Error("Shooting_profile_guesser::get_global_minimum_location: "
504  "global minimum does not exist");
505  }
506  } else {
507  if (alpha <= 0.) {
508  // potential is unbounded from below
509  throw Error("Shooting_profile_guesser::get_global_minimum_location: "
510  "global minimum does not exist");
511  } else if (alpha > 0. && alpha <= 0.25) {
512  global_min_loc = 0.25 * (3. - 4. * alpha) / alpha;
513  } else if (alpha > 0.25 && alpha <= 0.5) {
514  global_min_loc = 0.;
515  } else {
516  global_min_loc = 1.;
517  }
518  }
519  return global_min_loc;
520 }
521 
523  double rho, const Field_profiles& profile, int d) const
524 {
525  const auto potential_second = [this](double phi) {
526  return this->E * (-12. * this->alpha * phi * phi
527  + 6. * phi + (4. * this->alpha - 3.));
528  };
529 
530  const double local_min = calculate_false_min_location();
531  const double mass_squared = potential_second(local_min);
532 
533  const double rho_max = profile.get_domain_end();
534  const double y = rho * Sqrt(Abs(mass_squared));
535  const double y_max = rho_max * Sqrt(Abs(mass_squared));
536  const double phip_max = profile.derivative_at(0, 1, rho_max);
537  double correction = 0;
538  if (mass_squared > 0) {
539  if (d == 3) {
540  correction = -phip_max * rho_max * rho_max * Exp(y_max - y) /
541  (rho * (1. + y_max));
542  } else {
543  correction = -phip_max * rho_max * rho_max * BesselK(1, y) /
544  (rho * (BesselK(1, y_max) + 0.5 * y_max * (
545  BesselK(0, y_max)
546  + BesselK(2, y_max))));
547  }
548  } else {
549  throw Error("Shooting_profile_guesser::get_large_distance_solution: "
550  "cannot evaluate asymptotic solution for this potential");
551  }
552 
553  return local_min + correction;
554 }
555 
556 } // namespace BubbleProfiler
auto BesselK(Order v, Argument z) -> decltype(boost::math::cyl_bessel_k(v, z))
T Abs(T x) noexcept
double derivative_at(int field, int order, double rho) const
Take the radial derivative of a field at a given radius.
const Eigen::VectorXd & get_spatial_grid() const
Get a vector of the grid point coordinates.
void set_interpolation_points_fraction(double f)
T Exp(T x) noexcept
Field_profiles calculate_field_profiles(int, double, double, double, double)
void solve(const std::function< double(double)> &potential_, const std::function< double(double)> &potential_first_, const std::function< double(double)> &potential_second_, double false_min_, double true_min_, double barrier_, int dim_=3, unsigned int options=(Solver_options::Compute_action|Solver_options::Compute_profile))
Definition: shooting.cpp:81
T Sqrt(T x) noexcept
void log_message(Log_level level, const std::string &msg) const
const Field_profiles & get_bubble_profile() const
Definition: shooting.cpp:48
double quadratic_potential_solution(double) const
Definition: shooting.cpp:572
Eigen::VectorXd evaluate_at(double rho) const
Get all field values at a given radius.
Eigen::VectorXd update_spatial_grid(const Eigen::VectorXd &, double, double, int) const
virtual Potential * clone() const =0
Subclasses must implement a clone method.
double get_large_distance_solution(double, const Field_profiles &, int) const
virtual Field_profiles get_profile_guess(const Potential &, const Eigen::VectorXd &, int, double, double, double, double) override
Calculate an initial guess for the bubble profile.
Eigen::MatrixXd calculate_rotation_to_target(const Eigen::VectorXd &target)
Calculate the rotation matrix for a rotation to the target vector.
Definition: rotation.cpp:36
virtual std::size_t get_number_of_fields() const =0
Exception indicating general setup error.
Definition: error.hpp:32
void compute_vacuum_distance(const Potential &, const Eigen::VectorXd &)
Abstract base class for a generic potential.
Definition: potential.hpp:36
void calculate_potential_parameters(const Potential &, const Eigen::VectorXd &)
Discretized set of field profiles.