BubbleProfiler  0.3.0
by Peter Athron, Csaba Balazs, Michael Bardsley, Andrew Fowlie, Dylan Harries & Graham White
generic_perturbative_profiler.hpp
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 
18 #ifndef BUBBLEPROFILER_GENERIC_PERTURBATIVE_PROFILER_HPP_INCLUDED
19 #define BUBBLEPROFILER_GENERIC_PERTURBATIVE_PROFILER_HPP_INCLUDED
20 
26 #include "basic_logger.hpp"
28 #include "euclidean_action.hpp"
29 #include "error.hpp"
30 #include "field_profiles.hpp"
31 #include "gsl_interpolator.hpp"
32 #include "gsl_root_finder.hpp"
33 #include "kink_profile_guesser.hpp"
34 #include "observers.hpp"
36 #include "potential.hpp"
38 
39 #include <Eigen/Core>
40 
41 #include <algorithm>
42 #include <iterator>
43 #include <memory>
44 #include <numeric>
45 #include <sstream>
46 #include <tuple>
47 
48 namespace BubbleProfiler {
49 
70 template <class Integration_policy>
71 class Perturbative_profiler : public Integration_policy {
72 public:
73 
75  void set_domain_start(double start);
76 
78  void set_domain_end(double end);
79 
81  void set_initial_step_size(double h);
82 
86  }
87 
89  void set_number_of_dimensions(int d) { n_dims = d; }
90 
92  void set_initial_guesser(std::shared_ptr<Profile_guesser> g) {
93  guesser = g;
94  }
95 
96  void set_root_finder(std::shared_ptr<Root_finder<Eigen::VectorXd> > rf) {
97  root_finder = rf; }
98 
100  void set_convergence_tester(std::shared_ptr<Profile_convergence_tester> ct) {
101  convergence_tester = ct;
102  }
103 
105  void set_true_vacuum_loc(const Eigen::VectorXd& tv) { true_vacuum = tv; }
106 
108  void set_false_vacuum_loc(const Eigen::VectorXd& fv) { false_vacuum = fv; }
109 
115  template <class Observer>
116  void calculate_bubble_profile(Potential&, Observer&);
117 
123 
125  const Field_profiles& get_bubble_profile() const { return profiles; }
126 
128  double get_euclidean_action() const { return euclidean_action; }
129 
130 private:
131  double domain_start{-1.};
132  double domain_end{-1.};
133  double initial_step_size{1.e-2};
134  int n_grid_points{1000};
136  int n_dims{3};
137  int iteration{0};
139  double euclidean_action{0.};
140  std::shared_ptr<Profile_guesser> guesser{
141  std::make_shared<Kink_profile_guesser>()};
142  std::shared_ptr<Profile_convergence_tester> convergence_tester{
143  std::make_shared<Relative_convergence_tester>()};
144  std::shared_ptr<Root_finder<Eigen::VectorXd> > root_finder{
145  std::make_shared<GSL_root_finder<Eigen::Dynamic> >()};
147 
148  Eigen::VectorXd true_vacuum{};
149  Eigen::VectorXd false_vacuum{};
150 
151  void check_setup(Potential&) const;
153  int get_max_iterations() const;
154  bool check_convergence(const Potential&) const;
155 
157  Eigen::VectorXd&, double) const;
158  template <class Observer>
160  Eigen::VectorXd&, double, Observer) const;
161  std::tuple<Eigen::VectorXd,Eigen::MatrixXd> calculate_perturbation(
162  Potential&);
163  void integrate_system_to(const Perturbations_ODE_system&, double rho) const;
164 
165  void update_field_profiles(const Eigen::VectorXd&, const Eigen::MatrixXd&);
166 };
167 
168 template <class Integration_policy>
170  Potential& potential)
171 {
172  Dummy_observer observer;
173  calculate_bubble_profile(potential, observer);
174 }
175 
176 template <class Integration_policy>
177 template <class Observer>
179  Potential& potential, Observer& observer)
180 {
181  check_setup(potential);
182 
183  // Set up the solution field profiles and initialize BVP
185 
186  // Update domain boundaries
189 
190  // Compute required number of grid points to achieve the desired
191  // initial step size
192  n_grid_points = 1 + static_cast<int>(
193  std::ceil((domain_end - domain_start) / initial_step_size));
194 
195  const auto n_fields = profiles.get_number_of_fields();
196  Field_profiles perturbations(
197  Eigen::MatrixXd::Zero(n_grid_points, n_fields), domain_start,
199 
200  // Perturbation loop - keep going until convergence criteria
201  // are satisfied.
202  observer(profiles, perturbations);
203  const int max_iterations = get_max_iterations();
204 
205  iteration = 0;
206  bool converged = false;
207  while (iteration < max_iterations && !converged) {
208  logger.log_message(logging::Log_level::Trace, "Beginning perturbation "
209  + std::to_string(iteration + 1));
210 
211  const auto result = calculate_perturbation(potential);
212  perturbations = Field_profiles(std::get<0>(result), std::get<1>(result),
214 
215  update_field_profiles(perturbations.get_spatial_grid(),
216  perturbations.get_field_profiles());
217 
218  observer(profiles, perturbations);
219 
220  converged = check_convergence(potential);
221 
222  iteration++;
223  }
224 
225  if (!converged) {
227  }
228 
230 }
231 
232 template <class Integration_policy>
234 {
235  if (start < 0.) {
236  throw Domain_error("radial coordinates must be non-negative");
237  }
238  domain_start = start;
239 }
240 
241 template <class Integration_policy>
243 {
244  if (end < 0.) {
245  throw Domain_error("radial coordinates must be non-negative");
246  }
247  domain_end = end;
248 }
249 
250 template <class Integration_policy>
252 {
253  if (h <= 0.) {
254  throw Setup_error("integration step size must be positive");
255  }
256  initial_step_size = h;
257 }
258 
259 template <class Integration_policy>
261  Potential& potential) const
262 {
263  if (!convergence_tester) {
264  throw Setup_error("Perturbative_profiler::check_setup: "
265  "no convergence tester provided");
266  }
267 
268  if (!guesser) {
269  throw Setup_error("Perturbative_profiler::check_setup: "
270  "no profile guesser provided");
271  }
272 
273  if (!root_finder) {
274  throw Setup_error("Perturbative_profiler::check_setup: "
275  "no root finder provided");
276  }
277 
278  // Ensure false vacuum is located at the origin in field space
279  potential.translate_origin(false_vacuum);
280 }
281 
282 template <class Integration_policy>
284  Potential& potential) const
285 {
286  // We will first make a shift of fields such that the false vacuum is at the origin;
287  // this will remain the case for the rest of the calculation. The kink_profile_guesser
288  // uses a different basis, but does not affect the main potential.
289  Eigen::VectorXd true_vacuum_ = true_vacuum - false_vacuum;
290 
291  return guesser->get_profile_guess(potential, true_vacuum_, n_dims,
294 }
295 
296 template <class Integration_policy>
298 {
299  return convergence_tester->get_max_iterations();
300 }
301 
302 template <class Integration_policy>
304  const Potential& potential) const
305 {
306  return convergence_tester->is_converged(potential, profiles);
307 }
308 
309 template <class Integration_policy>
310 std::tuple<Eigen::VectorXd, Eigen::MatrixXd>
312  Potential& potential)
313 {
314  const auto n_fields = profiles.get_number_of_fields();
315 
316  Perturbations_ODE_system system(potential, profiles, n_dims);
317 
318  const Eigen::VectorXd domain_end_values(profiles.evaluate_at(domain_end));
319  Eigen::VectorXd domain_start_derivs(n_fields);
320  for (auto i = 0; i < n_fields; ++i) {
321  domain_start_derivs(i) = profiles.derivative_at(i, 1, domain_start);
322  }
323 
324  const auto boundary_conditions =
325  [n_fields, &domain_start_derivs, &domain_end_values]
326  (const Eigen::VectorXd& start_state, const Eigen::VectorXd& end_state) {
327  Eigen::VectorXd values(2 * n_fields);
328 
329  values.segment(0, n_fields)
330  = end_state.segment(0, n_fields) + domain_end_values;
331  values.segment(n_fields, n_fields)
332  = start_state.segment(n_fields, n_fields) + domain_start_derivs;
333 
334  return values;
335  };
336 
337  const double step_size = (domain_end - domain_start)
338  / (n_grid_points - 1.);
339 
340  const auto calculate_errors
341  = [this, &system, &boundary_conditions, step_size](
342  const Eigen::VectorXd& start_state) {
343  Eigen::VectorXd end_state(start_state);
344 
345  this->integrate_ivp(system, end_state, step_size);
346 
347  return boundary_conditions(start_state, end_state);
348  };
349 
350  Eigen::VectorXd initial_guess(Eigen::VectorXd::Zero(2 * n_fields));
351  initial_guess.segment(n_fields, n_fields) -= domain_start_derivs;
352 
353  const auto status = root_finder->find_root(calculate_errors, initial_guess);
354  if (status != Root_finder_status::SUCCESS) {
355  throw BVP_solver_error("Perturbative_profiler::calculate_perturbation: "
356  "unable to solve boundary value problem");
357  }
358 
359  const Eigen::VectorXd domain_start_perturbations(
360  root_finder->get_solution());
361  Eigen::VectorXd domain_end_perturbations(domain_start_perturbations);
362 
363  std::vector<double> coord_values;
364  std::vector<Eigen::VectorXd> perturbation_values;
365 
366  auto observer = [n_fields, &coord_values, &perturbation_values](
367  const Eigen::VectorXd& state, double rho) {
368  Eigen::VectorXd solution_values(
369  Eigen::VectorXd::Map(state.data(), n_fields));
370  perturbation_values.push_back(solution_values);
371  coord_values.push_back(rho);
372  };
373 
374  integrate_ivp(system, domain_end_perturbations, step_size, observer);
375 
376  // @todo reinstate final check on solution?
377 
378  const int n_perturbation_points = coord_values.size();
379  const Eigen::VectorXd grid_points(
380  Eigen::VectorXd::Map(coord_values.data(), n_perturbation_points));
381  Eigen::MatrixXd perturbations(n_perturbation_points, n_fields);
382  for (int i = 0; i < n_perturbation_points; ++i) {
383  perturbations.row(i) = perturbation_values[i];
384  }
385 
386  return std::make_tuple(grid_points, perturbations);
387 }
388 
389 template <class Integration_policy>
391  const Eigen::VectorXd& new_grid, const Eigen::MatrixXd& perturbations)
392 {
393  const Eigen::VectorXd old_grid = profiles.get_spatial_grid();
394  const Eigen::MatrixXd old_profiles = profiles.get_field_profiles();
395 
396  const int n_updated_points = new_grid.size();
397  const int n_fields = perturbations.cols();
398 
399  // if the new grid to be used extends beyond the limits of the
400  // previous grid, extend previous field profiles using constant
401  // extrapolation
402  const int n_old = old_grid.size();
403  const double old_domain_start = old_grid(0);
404  const double old_domain_end = old_grid(n_old - 1);
405 
406  const int n_before = (new_grid.array() < old_domain_start).count();
407  const int n_after = (new_grid.array() > old_domain_end).count();
408  const int extended_grid_size = n_before + n_old + n_after;
409  Eigen::VectorXd extended_grid(extended_grid_size);
410  Eigen::MatrixXd extended_profiles(extended_grid_size, n_fields);
411 
412  for (int i = 0; i < n_before; ++i) {
413  extended_grid(i) = new_grid(i);
414  extended_profiles.block(0, 0, n_before, n_fields).rowwise()
415  = old_profiles.row(0);
416  }
417  extended_grid.segment(n_before, n_old) = old_grid;
418  extended_profiles.block(n_before, 0, n_old, n_fields) = old_profiles;
419 
420  if (n_after > 0) {
421  int offset = new_grid.size();
422  while (new_grid(offset - 1) > old_domain_end) {
423  --offset;
424  }
425 
426  for (int i = 0; i < n_after; ++i) {
427  extended_grid(n_before + n_old + i) = new_grid(offset + i);
428  extended_profiles.block(n_before + n_old, 0, n_after, n_fields).rowwise()
429  = old_profiles.row(n_old - 1);
430  }
431  }
432 
433  Eigen::MatrixXd interpolated_profiles(n_updated_points, n_fields);
434  for (int i = 0; i < n_fields; ++i) {
435  interpolated_profiles.col(i) = interpolate_f_at(
436  extended_grid, extended_profiles.col(i), new_grid);
437  }
438 
439  const Eigen::MatrixXd new_profiles = interpolated_profiles + perturbations;
440 
441  profiles = Field_profiles(new_grid, new_profiles,
444 
445  domain_start = new_grid(0);
446  domain_end = new_grid(n_updated_points - 1);
447 }
448 
449 template <class Integration_policy>
451  const Perturbations_ODE_system& system,
452  Eigen::VectorXd& perturbations, double step_size) const
453 {
454  const auto dummy_observer = [](const Eigen::VectorXd&, double) {};
455  integrate_ivp(system, perturbations, step_size, dummy_observer);
456 }
457 
458 template <class Integration_policy>
459 template <class Observer>
461  const Perturbations_ODE_system& system,
462  Eigen::VectorXd& perturbations, double step_size, Observer observer) const
463 {
464  Integration_policy::integrate_system(system, perturbations,
465  domain_start, domain_end, step_size,
466  observer);
467 }
468 
469 } // namespace BubbleProfiler
470 
471 #endif
contains the definition of the Field_profiles clas
contains helper functions for calculating the Euclidean action
Exception indicating failure to integrate BVP.
Definition: error.hpp:44
A default observer that does not perform any actions when called.
Definition: observers.hpp:41
void set_initial_guesser(std::shared_ptr< Profile_guesser > g)
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.
Exception indicating failure of iterative procedure to converge.
Definition: error.hpp:54
void set_false_vacuum_loc(const Eigen::VectorXd &fv)
Exception indicating function evaluation out of allowed domain.
Definition: error.hpp:110
void integrate_system_to(const Perturbations_ODE_system &, double rho) const
void set_root_finder(std::shared_ptr< Root_finder< Eigen::VectorXd > > rf)
double get_euclidean_action() const
Retrieve the action after running calculate_bubble_profile.
void set_convergence_tester(std::shared_ptr< Profile_convergence_tester > ct)
void log_message(Log_level level, const std::string &msg) const
void integrate_ivp(const Perturbations_ODE_system &, Eigen::VectorXd &, double) const
void update_field_profiles(const Eigen::VectorXd &, const Eigen::MatrixXd &)
std::shared_ptr< Root_finder< Eigen::VectorXd > > root_finder
Eigen::VectorXd evaluate_at(double rho) const
Get all field values at a given radius.
Field_profiles construct_initial_profiles(Potential &) const
Eigen::VectorXd interpolate_f_at(const Eigen::VectorXd &x_old, const Eigen::VectorXd &y_old, const Eigen::VectorXd &x_new)
Computes interpolated function values at a new set of grid points.
contains the definition of the GSL_interpolator class
std::tuple< Eigen::VectorXd, Eigen::MatrixXd > calculate_perturbation(Potential &)
Exception indicating general setup error.
Definition: error.hpp:32
std::shared_ptr< Profile_convergence_tester > convergence_tester
Abstract base class for a generic potential.
Definition: potential.hpp:36
virtual void translate_origin(const Eigen::VectorXd &translation)=0
Shift the location of the origin by a specified vector.
double calculate_action(const Potential &potential, const Field_profiles &profiles, std::size_t max_intervals=1000, double rel_tol=1.e-4, double abs_tol=1.e-4, Integration_rule rule=Integration_rule::GK31, bool use_kinetic=true)
Calculates the action using either the kinetic or potential terms.
const Field_profiles & get_bubble_profile() const
Retrieve the field profiles after running calculate_bubble_profile.
Discretized set of field profiles.
Bounce solver using perturbative method.
const Eigen::MatrixXd & get_field_profiles() const
Get the field profile data in matrix form.
Class implementing the system of ODEs obeyed by profile perturbations.