oomph::BlockHopfLinearSolver Class Reference

#include <assembly_handler.h>

+ Inheritance diagram for oomph::BlockHopfLinearSolver:

Public Member Functions

 BlockHopfLinearSolver (LinearSolver *const linear_solver_pt)
 Constructor, inherits the original linear solver. More...
 
 ~BlockHopfLinearSolver ()
 Destructor: clean up the allocated memory. More...
 
void solve_for_two_rhs (Problem *const &problem_pt, DoubleVector &result, const DoubleVector &rhs2, DoubleVector &result2)
 Solve for two right hand sides. More...
 
void solve (Problem *const &problem_pt, DoubleVector &result)
 The solve function uses the block factorisation. More...
 
void solve (DoubleMatrixBase *const &matrix_pt, const DoubleVector &rhs, DoubleVector &result)
 
void solve (DoubleMatrixBase *const &matrix_pt, const Vector< double > &rhs, Vector< double > &result)
 
void resolve (const DoubleVector &rhs, DoubleVector &result)
 The resolve function also uses the block factorisation. More...
 
LinearSolverlinear_solver_pt () const
 Access function to the original linear solver. More...
 
- Public Member Functions inherited from oomph::LinearSolver
 LinearSolver ()
 Empty constructor, initialise the member data. More...
 
 LinearSolver (const LinearSolver &dummy)=delete
 Broken copy constructor. More...
 
void operator= (const LinearSolver &)=delete
 Broken assignment operator. More...
 
virtual ~LinearSolver ()
 Empty virtual destructor. More...
 
void enable_doc_time ()
 Enable documentation of solve times. More...
 
void disable_doc_time ()
 Disable documentation of solve times. More...
 
bool is_doc_time_enabled () const
 Is documentation of solve times enabled? More...
 
bool is_resolve_enabled () const
 Boolean flag indicating if resolves are enabled. More...
 
virtual void enable_resolve ()
 
virtual void disable_resolve ()
 
virtual void solve_transpose (Problem *const &problem_pt, DoubleVector &result)
 
virtual void solve_transpose (DoubleMatrixBase *const &matrix_pt, const DoubleVector &rhs, DoubleVector &result)
 
virtual void solve_transpose (DoubleMatrixBase *const &matrix_pt, const Vector< double > &rhs, Vector< double > &result)
 
virtual void resolve_transpose (const DoubleVector &rhs, DoubleVector &result)
 
virtual void clean_up_memory ()
 
virtual double jacobian_setup_time () const
 
virtual double linear_solver_solution_time () const
 
virtual void enable_computation_of_gradient ()
 
void disable_computation_of_gradient ()
 
void reset_gradient ()
 
void get_gradient (DoubleVector &gradient)
 function to access the gradient, provided it has been computed More...
 
- Public Member Functions inherited from oomph::DistributableLinearAlgebraObject
 DistributableLinearAlgebraObject ()
 Default constructor - create a distribution. More...
 
 DistributableLinearAlgebraObject (const DistributableLinearAlgebraObject &matrix)=delete
 Broken copy constructor. More...
 
void operator= (const DistributableLinearAlgebraObject &)=delete
 Broken assignment operator. More...
 
virtual ~DistributableLinearAlgebraObject ()
 Destructor. More...
 
LinearAlgebraDistributiondistribution_pt () const
 access to the LinearAlgebraDistribution More...
 
unsigned nrow () const
 access function to the number of global rows. More...
 
unsigned nrow_local () const
 access function for the num of local rows on this processor. More...
 
unsigned nrow_local (const unsigned &p) const
 access function for the num of local rows on this processor. More...
 
unsigned first_row () const
 access function for the first row on this processor More...
 
unsigned first_row (const unsigned &p) const
 access function for the first row on this processor More...
 
bool distributed () const
 distribution is serial or distributed More...
 
bool distribution_built () const
 
void build_distribution (const LinearAlgebraDistribution *const dist_pt)
 
void build_distribution (const LinearAlgebraDistribution &dist)
 

Private Attributes

LinearSolverLinear_solver_pt
 Pointer to the original linear solver. More...
 
ProblemProblem_pt
 Pointer to the problem, used in the resolve. More...
 
DoubleVectorA_pt
 Pointer to the storage for the vector a. More...
 
DoubleVectorE_pt
 Pointer to the storage for the vector e (0 to n-1) More...
 
DoubleVectorG_pt
 Pointer to the storage for the vector g (0 to n-1) More...
 

Additional Inherited Members

- Protected Member Functions inherited from oomph::DistributableLinearAlgebraObject
void clear_distribution ()
 
- Protected Attributes inherited from oomph::LinearSolver
bool Enable_resolve
 
bool Doc_time
 Boolean flag that indicates whether the time taken. More...
 
bool Compute_gradient
 
bool Gradient_has_been_computed
 flag that indicates whether the gradient was computed or not More...
 
DoubleVector Gradient_for_glob_conv_newton_solve
 

Detailed Description

A custom linear solver class that is used to solve a block-factorised version of the Hopf bifurcation detection problem.

Constructor & Destructor Documentation

◆ BlockHopfLinearSolver()

oomph::BlockHopfLinearSolver::BlockHopfLinearSolver ( LinearSolver *const  linear_solver_pt)
inline

Constructor, inherits the original linear solver.

975  Problem_pt(0),
976  A_pt(0),
977  E_pt(0),
978  G_pt(0)
979  {
980  }
LinearSolver * Linear_solver_pt
Pointer to the original linear solver.
Definition: assembly_handler.h:957
DoubleVector * A_pt
Pointer to the storage for the vector a.
Definition: assembly_handler.h:963
Problem * Problem_pt
Pointer to the problem, used in the resolve.
Definition: assembly_handler.h:960
DoubleVector * E_pt
Pointer to the storage for the vector e (0 to n-1)
Definition: assembly_handler.h:966
DoubleVector * G_pt
Pointer to the storage for the vector g (0 to n-1)
Definition: assembly_handler.h:969
LinearSolver * linear_solver_pt() const
Access function to the original linear solver.
Definition: assembly_handler.h:1023

◆ ~BlockHopfLinearSolver()

oomph::BlockHopfLinearSolver::~BlockHopfLinearSolver ( )

Destructor: clean up the allocated memory.

Clean up the memory that may have been allocated by the solver.

3602  {
3603  if (A_pt != 0)
3604  {
3605  delete A_pt;
3606  }
3607  if (E_pt != 0)
3608  {
3609  delete E_pt;
3610  }
3611  if (G_pt != 0)
3612  {
3613  delete G_pt;
3614  }
3615  }

References A_pt, E_pt, and G_pt.

Member Function Documentation

◆ linear_solver_pt()

LinearSolver* oomph::BlockHopfLinearSolver::linear_solver_pt ( ) const
inline

Access function to the original linear solver.

1024  {
1025  return Linear_solver_pt;
1026  }

References Linear_solver_pt.

Referenced by oomph::HopfHandler::~HopfHandler().

◆ resolve()

void oomph::BlockHopfLinearSolver::resolve ( const DoubleVector rhs,
DoubleVector result 
)
virtual

The resolve function also uses the block factorisation.

Reimplemented from oomph::LinearSolver.

4419  {
4420  throw OomphLibError("resolve() is not implemented for this solver",
4423  }
#define OOMPH_EXCEPTION_LOCATION
Definition: oomph_definitions.h:61
#define OOMPH_CURRENT_FUNCTION
Definition: oomph_definitions.h:86

References OOMPH_CURRENT_FUNCTION, and OOMPH_EXCEPTION_LOCATION.

◆ solve() [1/3]

void oomph::BlockHopfLinearSolver::solve ( DoubleMatrixBase *const &  matrix_pt,
const DoubleVector rhs,
DoubleVector result 
)
inlinevirtual

The linear-algebra-type solver does not make sense. The interface is deliberately broken

Reimplemented from oomph::LinearSolver.

999  {
1000  throw OomphLibError(
1001  "Linear-algebra interface does not make sense for this linear solver\n",
1004  }

References OOMPH_CURRENT_FUNCTION, and OOMPH_EXCEPTION_LOCATION.

◆ solve() [2/3]

void oomph::BlockHopfLinearSolver::solve ( DoubleMatrixBase *const &  matrix_pt,
const Vector< double > &  rhs,
Vector< double > &  result 
)
inlinevirtual

The linear-algebra-type solver does not make sense. The interface is deliberately broken

Reimplemented from oomph::LinearSolver.

1011  {
1012  throw OomphLibError(
1013  "Linear-algebra interface does not make sense for this linear solver\n",
1016  }

References OOMPH_CURRENT_FUNCTION, and OOMPH_EXCEPTION_LOCATION.

◆ solve() [3/3]

void oomph::BlockHopfLinearSolver::solve ( Problem *const &  problem_pt,
DoubleVector result 
)
virtual

The solve function uses the block factorisation.

Use a block factorisation to solve the augmented system associated with a Hopf bifurcation.

Implements oomph::LinearSolver.

3623  {
3624  // if the result is setup then it should not be distributed
3625 #ifdef PARANOID
3626  if (result.built())
3627  {
3628  if (result.distributed())
3629  {
3630  throw OomphLibError("The result vector must not be distributed",
3633  }
3634  }
3635 #endif
3636 
3637  // Locally cache the pointer to the handler.
3638  HopfHandler* handler_pt =
3639  static_cast<HopfHandler*>(problem_pt->assembly_handler_pt());
3640 
3641  // Find the number of dofs in the augmented problem
3642  unsigned n_dof = problem_pt->ndof();
3643 
3644  // create the linear algebra distribution for this solver
3645  // currently only global (non-distributed) distributions are allowed
3646  LinearAlgebraDistribution dist(problem_pt->communicator_pt(), n_dof, false);
3647  this->build_distribution(dist);
3648 
3649  // Firstly, let's calculate the derivative of the residuals wrt
3650  // the parameter
3651  DoubleVector dRdparam(this->distribution_pt(), 0.0);
3652 
3653  const double FD_step = 1.0e-8;
3654  {
3655  // Cache pointer to the parameter (second last entry in the vector
3656  double* param_pt = &problem_pt->dof(n_dof - 2);
3657  // Backup the parameter
3658  double old_var = *param_pt;
3659  ;
3660  // Increment the parameter
3661  *param_pt += FD_step;
3662  problem_pt->actions_after_change_in_bifurcation_parameter();
3663 
3664  // Now calculate the new residuals
3665  problem_pt->get_residuals(dRdparam);
3666 
3667  // Now calculate the difference assume original residuals in resul
3668  for (unsigned n = 0; n < n_dof; n++)
3669  {
3670  dRdparam[n] = (dRdparam[n] - result[n]) / FD_step;
3671  }
3672 
3673  // Reset the parameter
3674  *param_pt = old_var;
3675  problem_pt->actions_after_change_in_bifurcation_parameter();
3676  }
3677 
3678  // Switch the handler to "standard" mode
3679  handler_pt->solve_standard_system();
3680 
3681  // Find out the number of dofs in the non-standard problem
3682  n_dof = problem_pt->ndof();
3683 
3684  // update the distribution pt
3685  dist.build(problem_pt->communicator_pt(), n_dof, false);
3686  this->build_distribution(dist);
3687 
3688  // Setup storage for temporary vector
3689  DoubleVector y1(this->distribution_pt(), 0.0),
3690  alpha(this->distribution_pt(), 0.0);
3691 
3692  // Allocate storage for A which can be used in the resolve
3693  if (A_pt != 0)
3694  {
3695  delete A_pt;
3696  }
3697  A_pt = new DoubleVector(this->distribution_pt(), 0.0);
3698 
3699  // We are going to do resolves using the underlying linear solver
3701 
3702  // Solve the first system Jy1 = R
3703  Linear_solver_pt->solve(problem_pt, y1);
3704 
3705  // This should have set the sign of the jacobian, store it
3706  int sign_of_jacobian = problem_pt->sign_of_jacobian();
3707 
3708  // Now let's get the appropriate bit of alpha
3709  for (unsigned n = 0; n < n_dof; n++)
3710  {
3711  alpha[n] = dRdparam[n];
3712  }
3713 
3714  // Resolve to find A
3716 
3717  // Now set to the complex system
3718  handler_pt->solve_complex_system();
3719 
3720  // update the distribution
3721  dist.build(problem_pt->communicator_pt(), n_dof * 2, false);
3722  this->build_distribution(dist);
3723 
3724  // Resize the stored vector G
3725  if (G_pt != 0)
3726  {
3727  delete G_pt;
3728  }
3729  G_pt = new DoubleVector(this->distribution_pt(), 0.0);
3730 
3731  // Solve the first Zg = (Mz -My)
3732  Linear_solver_pt->solve(problem_pt, *G_pt);
3733 
3734  // This should have set the sign of the complex matrix's determinant,
3735  // multiply
3736  sign_of_jacobian *= problem_pt->sign_of_jacobian();
3737 
3738  // We can now construct our multipliers
3739  // Prepare to scale
3740  double dof_length = 0.0, a_length = 0.0, y1_length = 0.0;
3741  // Loop over the standard number of dofs
3742  for (unsigned n = 0; n < n_dof; n++)
3743  {
3744  if (std::fabs(problem_pt->dof(n)) > dof_length)
3745  {
3746  dof_length = std::fabs(problem_pt->dof(n));
3747  }
3748  if (std::fabs((*A_pt)[n]) > a_length)
3749  {
3750  a_length = std::fabs((*A_pt)[n]);
3751  }
3752  if (std::fabs(y1[n]) > y1_length)
3753  {
3754  y1_length = std::fabs(y1[n]);
3755  }
3756  }
3757 
3758  double a_mult = dof_length / a_length;
3759  double y1_mult = dof_length / y1_length;
3760  a_mult += FD_step;
3761  y1_mult += FD_step;
3762  a_mult *= FD_step;
3763  y1_mult *= FD_step;
3764 
3765  // Local storage for the product terms
3766  Vector<double> Jprod_a(2 * n_dof, 0.0), Jprod_y1(2 * n_dof, 0.0);
3767 
3768  // Temporary storage
3769  Vector<double> rhs(2 * n_dof);
3770 
3771  // find the number of elements
3772  unsigned long n_element = problem_pt->mesh_pt()->nelement();
3773 
3774  // Calculate the product of the jacobian matrices, etc
3775  for (unsigned long e = 0; e < n_element; e++)
3776  {
3777  GeneralisedElement* elem_pt = problem_pt->mesh_pt()->element_pt(e);
3778  // Loop over the ndofs in each element
3779  unsigned n_var = elem_pt->ndof();
3780  // Get the jacobian matrices
3781  DenseMatrix<double> jac(n_var), jac_a(n_var), jac_y1(n_var);
3782  DenseMatrix<double> M(n_var), M_a(n_var), M_y1(n_var);
3783  // Get unperturbed jacobian
3784  elem_pt->get_jacobian_and_mass_matrix(rhs, jac, M);
3785 
3786  // Backup the dofs
3787  Vector<double> dof_bac(n_var);
3788  // Perturb the original dofs
3789  for (unsigned n = 0; n < n_var; n++)
3790  {
3791  unsigned eqn_number = elem_pt->eqn_number(n);
3792  dof_bac[n] = problem_pt->dof(eqn_number);
3793  // Pertub by vector A
3794  problem_pt->dof(eqn_number) += a_mult * (*A_pt)[eqn_number];
3795  }
3796 
3797  // Now get the new jacobian and mass matrix
3798  elem_pt->get_jacobian_and_mass_matrix(rhs, jac_a, M_a);
3799 
3800  // Perturb the dofs
3801  for (unsigned n = 0; n < n_var; n++)
3802  {
3803  unsigned eqn_number = elem_pt->eqn_number(n);
3804  problem_pt->dof(eqn_number) = dof_bac[n];
3805  // Pertub by vector y1
3806  problem_pt->dof(eqn_number) += y1_mult * y1[eqn_number];
3807  }
3808 
3809  // Now get the new jacobian and mass matrix
3810  elem_pt->get_jacobian_and_mass_matrix(rhs, jac_y1, M_y1);
3811 
3812  // Reset the dofs
3813  for (unsigned n = 0; n < n_var; n++)
3814  {
3815  unsigned eqn_number = elem_pt->eqn_number(n);
3816  problem_pt->dof(eqn_number) = dof_bac[n];
3817  }
3818 
3819  // OK, now work out the products
3820  for (unsigned n = 0; n < n_var; n++)
3821  {
3822  unsigned eqn_number = elem_pt->eqn_number(n);
3823  double prod_a1 = 0.0, prod_y11 = 0.0;
3824  double prod_a2 = 0.0, prod_y12 = 0.0;
3825  for (unsigned m = 0; m < n_var; m++)
3826  {
3827  const unsigned unknown = elem_pt->eqn_number(m);
3828  const double y = handler_pt->Phi[unknown];
3829  const double z = handler_pt->Psi[unknown];
3830  const double omega = handler_pt->Omega;
3831  // Real part (first line)
3832  prod_a1 +=
3833  (jac_a(n, m) - jac(n, m)) * y + omega * (M_a(n, m) - M(n, m)) * z;
3834  prod_y11 +=
3835  (jac_y1(n, m) - jac(n, m)) * y + omega * (M_y1(n, m) - M(n, m)) * z;
3836  // Imag par (second line)
3837  prod_a2 +=
3838  (jac_a(n, m) - jac(n, m)) * z - omega * (M_a(n, m) - M(n, m)) * y;
3839  prod_y12 +=
3840  (jac_y1(n, m) - jac(n, m)) * z - omega * (M_y1(n, m) - M(n, m)) * y;
3841  }
3842  Jprod_a[eqn_number] += prod_a1 / a_mult;
3843  Jprod_y1[eqn_number] += prod_y11 / y1_mult;
3844  Jprod_a[n_dof + eqn_number] += prod_a2 / a_mult;
3845  Jprod_y1[n_dof + eqn_number] += prod_y12 / y1_mult;
3846  }
3847  }
3848 
3849  // The assumption here is still that the result has been set to the
3850  // residuals.
3851  for (unsigned n = 0; n < 2 * n_dof; n++)
3852  {
3853  rhs[n] = result[n_dof + n] - Jprod_y1[n];
3854  }
3855 
3856  // Temporary storage
3857  DoubleVector y2(this->distribution_pt(), 0.0);
3858 
3859  // DoubleVector for rhs
3860  DoubleVector rhs2(this->distribution_pt(), 0.0);
3861  for (unsigned i = 0; i < 2 * n_dof; i++)
3862  {
3863  rhs2[i] = rhs[i];
3864  }
3865 
3866  // Solve it
3867  Linear_solver_pt->resolve(rhs2, y2);
3868 
3869  // Assemble the next RHS
3870  for (unsigned n = 0; n < 2 * n_dof; n++)
3871  {
3872  rhs[n] = dRdparam[n_dof + n] - Jprod_a[n];
3873  }
3874 
3875  // Resive the storage
3876  if (E_pt != 0)
3877  {
3878  delete E_pt;
3879  }
3880  E_pt = new DoubleVector(this->distribution_pt(), 0.0);
3881 
3882  // Solve for the next RHS
3883  for (unsigned i = 0; i < 2 * n_dof; i++)
3884  {
3885  rhs2[i] = rhs[i];
3886  }
3887  Linear_solver_pt->resolve(rhs2, *E_pt);
3888 
3889  // We can now calculate the final corrections
3890  // We need to work out a large number of dot products
3891  double dot_c = 0.0, dot_d = 0.0, dot_e = 0.0, dot_f = 0.0, dot_g = 0.0;
3892  double dot_h = 0.0;
3893 
3894  for (unsigned n = 0; n < n_dof; n++)
3895  {
3896  // Get the appopriate entry
3897  const double Cn = handler_pt->C[n];
3898  dot_c += Cn * y2[n];
3899  dot_d += Cn * y2[n_dof + n];
3900  dot_e += Cn * (*E_pt)[n];
3901  dot_f += Cn * (*E_pt)[n_dof + n];
3902  dot_g += Cn * (*G_pt)[n];
3903  dot_h += Cn * (*G_pt)[n_dof + n];
3904  }
3905 
3906  // Now we should be able to work out the corrections
3907  double denom = dot_e * dot_h - dot_g * dot_f;
3908 
3909  // Copy the previous residuals
3910  double R31 = result[3 * n_dof], R32 = result[3 * n_dof + 1];
3911  // Delta parameter
3912  const double delta_param =
3913  ((R32 - dot_d) * dot_g - (R31 - dot_c) * dot_h) / denom;
3914  // Delta frequency
3915  const double delta_w = -((R32 - dot_d) + dot_f * delta_param) / (dot_h);
3916 
3917  // Load into the result vector
3918  result[3 * n_dof] = delta_param;
3919  result[3 * n_dof + 1] = delta_w;
3920 
3921  // The corrections to the null vector
3922  for (unsigned n = 0; n < 2 * n_dof; n++)
3923  {
3924  result[n_dof + n] =
3925  y2[n] - (*E_pt)[n] * delta_param - (*G_pt)[n] * delta_w;
3926  }
3927 
3928  // Finally add the corrections to the unknowns
3929  for (unsigned n = 0; n < n_dof; n++)
3930  {
3931  result[n] = y1[n] - (*A_pt)[n] * delta_param;
3932  }
3933 
3934  // The sign of the jacobian is the previous signs multiplied by the
3935  // sign of the denominator
3936  problem_pt->sign_of_jacobian() =
3937  sign_of_jacobian * static_cast<int>(std::fabs(denom) / denom);
3938 
3939  // Switch things to our full solver
3940  handler_pt->solve_full_system();
3941 
3942  // If we are not storing things, clear the results
3943  if (!Enable_resolve)
3944  {
3945  // We no longer need to store the matrix
3947  delete A_pt;
3948  A_pt = 0;
3949  delete E_pt;
3950  E_pt = 0;
3951  delete G_pt;
3952  G_pt = 0;
3953  }
3954  // Otherwise, also store the problem pointer
3955  else
3956  {
3957  Problem_pt = problem_pt;
3958  }
3959  }
int i
Definition: BiCGSTAB_step_by_step.cpp:9
const unsigned n
Definition: CG3DPackingUnitTest.cpp:11
std::vector< double > DoubleVector
loads clump configuration
Definition: ClumpInput.h:26
Array< double, 1, 3 > e(1./3., 0.5, 2.)
Matrix< RealScalar, Dynamic, Dynamic > M
Definition: bench_gemm.cpp:50
LinearAlgebraDistribution * distribution_pt() const
access to the LinearAlgebraDistribution
Definition: linear_algebra_distribution.h:457
void build_distribution(const LinearAlgebraDistribution *const dist_pt)
Definition: linear_algebra_distribution.h:507
virtual void solve(Problem *const &problem_pt, DoubleVector &result)=0
virtual void enable_resolve()
Definition: linear_solver.h:135
virtual void resolve(const DoubleVector &rhs, DoubleVector &result)
Definition: linear_solver.h:225
bool Enable_resolve
Definition: linear_solver.h:73
virtual void disable_resolve()
Definition: linear_solver.h:144
Scalar * y
Definition: level1_cplx_impl.h:128
RealScalar alpha
Definition: level1_cplx_impl.h:151
int * m
Definition: level2_cplx_impl.h:294
Vector::Scalar omega(const Vector &t, const Vector &s, RealScalar angle)
Definition: IDRS.h:36
Real fabs(const Real &a)
Definition: boostmultiprec.cpp:117
double FD_step
FD step.
Definition: black_box_newton_solver.cc:54

References A_pt, oomph::Problem::actions_after_change_in_bifurcation_parameter(), alpha, oomph::Problem::assembly_handler_pt(), oomph::LinearAlgebraDistribution::build(), oomph::DistributableLinearAlgebraObject::build_distribution(), oomph::DoubleVector::built(), oomph::HopfHandler::C, oomph::Problem::communicator_pt(), oomph::LinearSolver::disable_resolve(), oomph::DistributableLinearAlgebraObject::distributed(), oomph::DistributableLinearAlgebraObject::distribution_pt(), oomph::Problem::dof(), e(), E_pt, oomph::Mesh::element_pt(), oomph::LinearSolver::Enable_resolve, oomph::LinearSolver::enable_resolve(), oomph::GeneralisedElement::eqn_number(), boost::multiprecision::fabs(), oomph::BlackBoxFDNewtonSolver::FD_step, G_pt, oomph::GeneralisedElement::get_jacobian_and_mass_matrix(), oomph::Problem::get_residuals(), i, Linear_solver_pt, m, oomph::Problem::mesh_pt(), n, oomph::GeneralisedElement::ndof(), oomph::Problem::ndof(), oomph::Mesh::nelement(), Eigen::internal::omega(), oomph::HopfHandler::Omega, OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION, oomph::HopfHandler::Phi, Problem_pt, oomph::HopfHandler::Psi, oomph::LinearSolver::resolve(), oomph::Problem::sign_of_jacobian(), oomph::LinearSolver::solve(), oomph::HopfHandler::solve_complex_system(), oomph::HopfHandler::solve_full_system(), oomph::HopfHandler::solve_standard_system(), and y.

◆ solve_for_two_rhs()

void oomph::BlockHopfLinearSolver::solve_for_two_rhs ( Problem *const &  problem_pt,
DoubleVector result,
const DoubleVector rhs2,
DoubleVector result2 
)

Solve for two right hand sides.

Solve for two right hand sides, required for (efficient) continuation because otherwise we have to store the inverse of the jacobian and the complex equivalent ... nasty.

3970  {
3971  // if the result is setup then it should not be distributed
3972 #ifdef PARANOID
3973  if (result.built())
3974  {
3975  if (result.distributed())
3976  {
3977  throw OomphLibError("The result vector must not be distributed",
3980  }
3981  }
3982  if (result2.built())
3983  {
3984  if (result2.distributed())
3985  {
3986  throw OomphLibError("The result2 vector must not be distributed",
3989  }
3990  }
3991 #endif
3992 
3993  // Locally cache the pointer to the handler.
3994  HopfHandler* handler_pt =
3995  static_cast<HopfHandler*>(problem_pt->assembly_handler_pt());
3996 
3997  // Find the number of dofs in the augmented problem
3998  unsigned n_dof = problem_pt->ndof();
3999 
4000  // create the linear algebra distribution for this solver
4001  // currently only global (non-distributed) distributions are allowed
4002  LinearAlgebraDistribution dist(problem_pt->communicator_pt(), n_dof, false);
4003  this->build_distribution(dist);
4004 
4005  // if the result vector is not setup then rebuild with distribution = global
4006  if (!result.built())
4007  {
4008  result.build(this->distribution_pt(), 0.0);
4009  }
4010  if (!result2.built())
4011  {
4012  result2.build(this->distribution_pt(), 0.0);
4013  }
4014 
4015  // Firstly, let's calculate the derivative of the residuals wrt
4016  // the parameter
4017  DoubleVector dRdparam(this->distribution_pt(), 0.0);
4018 
4019  const double FD_step = 1.0e-8;
4020  {
4021  // Cache pointer to the parameter (second last entry in the vector
4022  double* param_pt = &problem_pt->dof(n_dof - 2);
4023  // Backup the parameter
4024  double old_var = *param_pt;
4025  ;
4026  // Increment the parameter
4027  *param_pt += FD_step;
4028  problem_pt->actions_after_change_in_bifurcation_parameter();
4029 
4030  // Now calculate the new residuals
4031  problem_pt->get_residuals(dRdparam);
4032 
4033  // Now calculate the difference assume original residuals in resul
4034  for (unsigned n = 0; n < n_dof; n++)
4035  {
4036  dRdparam[n] = (dRdparam[n] - result[n]) / FD_step;
4037  }
4038 
4039  // Reset the parameter
4040  *param_pt = old_var;
4041  problem_pt->actions_after_change_in_bifurcation_parameter();
4042  }
4043 
4044  // Switch the handler to "standard" mode
4045  handler_pt->solve_standard_system();
4046 
4047  // Find out the number of dofs in the non-standard problem
4048  n_dof = problem_pt->ndof();
4049 
4050  //
4051  dist.build(problem_pt->communicator_pt(), n_dof, false);
4052  this->build_distribution(dist);
4053 
4054  // Setup storage for temporary vector
4055  DoubleVector y1(this->distribution_pt(), 0.0),
4056  alpha(this->distribution_pt(), 0.0),
4057  y1_resolve(this->distribution_pt(), 0.0);
4058 
4059  // Allocate storage for A which can be used in the resolve
4060  if (A_pt != 0)
4061  {
4062  delete A_pt;
4063  }
4064  A_pt = new DoubleVector(this->distribution_pt(), 0.0);
4065 
4066  // We are going to do resolves using the underlying linear solver
4068 
4069  // Solve the first system Jy1 =
4070  Linear_solver_pt->solve(problem_pt, y1);
4071 
4072  // This should have set the sign of the jacobian, store it
4073  int sign_of_jacobian = problem_pt->sign_of_jacobian();
4074 
4075  // Now let's get the appropriate bit of alpha
4076  for (unsigned n = 0; n < n_dof; n++)
4077  {
4078  alpha[n] = dRdparam[n];
4079  }
4080 
4081  // Resolve to find A
4083 
4084  // Get the solution for the second rhs
4085  for (unsigned n = 0; n < n_dof; n++)
4086  {
4087  alpha[n] = rhs2[n];
4088  }
4089 
4090  // Resolve to find y1_resolve
4091  Linear_solver_pt->resolve(alpha, y1_resolve);
4092 
4093  // Now set to the complex system
4094  handler_pt->solve_complex_system();
4095 
4096  // rebuild the Distribution
4097  dist.build(problem_pt->communicator_pt(), n_dof * 2, false);
4098  this->build_distribution(dist);
4099 
4100  // Resize the stored vector G
4101  if (G_pt != 0)
4102  {
4103  delete G_pt;
4104  }
4105  G_pt = new DoubleVector(this->distribution_pt(), 0.0);
4106 
4107  // Solve the first Zg = (Mz -My)
4108  Linear_solver_pt->solve(problem_pt, *G_pt);
4109 
4110  // This should have set the sign of the complex matrix's determinant,
4111  // multiply
4112  sign_of_jacobian *= problem_pt->sign_of_jacobian();
4113 
4114  // We can now construct our multipliers
4115  // Prepare to scale
4116  double dof_length = 0.0, a_length = 0.0, y1_length = 0.0,
4117  y1_resolve_length = 0.0;
4118  // Loop over the standard number of dofs
4119  for (unsigned n = 0; n < n_dof; n++)
4120  {
4121  if (std::fabs(problem_pt->dof(n)) > dof_length)
4122  {
4123  dof_length = std::fabs(problem_pt->dof(n));
4124  }
4125  if (std::fabs((*A_pt)[n]) > a_length)
4126  {
4127  a_length = std::fabs((*A_pt)[n]);
4128  }
4129  if (std::fabs(y1[n]) > y1_length)
4130  {
4131  y1_length = std::fabs(y1[n]);
4132  }
4133  if (std::fabs(y1_resolve[n]) > y1_resolve_length)
4134  {
4135  y1_resolve_length = std::fabs(y1[n]);
4136  }
4137  }
4138 
4139 
4140  double a_mult = dof_length / a_length;
4141  double y1_mult = dof_length / y1_length;
4142  double y1_resolve_mult = dof_length / y1_resolve_length;
4143  a_mult += FD_step;
4144  y1_mult += FD_step;
4145  y1_resolve_mult += FD_step;
4146  a_mult *= FD_step;
4147  y1_mult *= FD_step;
4148  y1_resolve_mult *= FD_step;
4149 
4150  // Local storage for the product terms
4151  Vector<double> Jprod_a(2 * n_dof, 0.0), Jprod_y1(2 * n_dof, 0.0);
4152  Vector<double> Jprod_y1_resolve(2 * n_dof, 0.0);
4153 
4154  // Temporary storage
4155  Vector<double> rhs(2 * n_dof);
4156 
4157  // find the number of elements
4158  unsigned long n_element = problem_pt->mesh_pt()->nelement();
4159 
4160  // Calculate the product of the jacobian matrices, etc
4161  for (unsigned long e = 0; e < n_element; e++)
4162  {
4163  GeneralisedElement* elem_pt = problem_pt->mesh_pt()->element_pt(e);
4164  // Loop over the ndofs in each element
4165  unsigned n_var = elem_pt->ndof();
4166  // Get the jacobian matrices
4167  DenseMatrix<double> jac(n_var), jac_a(n_var), jac_y1(n_var),
4168  jac_y1_resolve(n_var);
4169  DenseMatrix<double> M(n_var), M_a(n_var), M_y1(n_var),
4170  M_y1_resolve(n_var);
4171  // Get unperturbed jacobian
4172  elem_pt->get_jacobian_and_mass_matrix(rhs, jac, M);
4173 
4174  // Backup the dofs
4175  Vector<double> dof_bac(n_var);
4176  // Perturb the original dofs
4177  for (unsigned n = 0; n < n_var; n++)
4178  {
4179  unsigned eqn_number = elem_pt->eqn_number(n);
4180  dof_bac[n] = problem_pt->dof(eqn_number);
4181  // Pertub by vector A
4182  problem_pt->dof(eqn_number) += a_mult * (*A_pt)[eqn_number];
4183  }
4184 
4185  // Now get the new jacobian and mass matrix
4186  elem_pt->get_jacobian_and_mass_matrix(rhs, jac_a, M_a);
4187 
4188  // Perturb the dofs
4189  for (unsigned n = 0; n < n_var; n++)
4190  {
4191  unsigned eqn_number = elem_pt->eqn_number(n);
4192  problem_pt->dof(eqn_number) = dof_bac[n];
4193  // Pertub by vector y1
4194  problem_pt->dof(eqn_number) += y1_mult * y1[eqn_number];
4195  }
4196 
4197  // Now get the new jacobian and mass matrix
4198  elem_pt->get_jacobian_and_mass_matrix(rhs, jac_y1, M_y1);
4199 
4200  // Perturb the dofs
4201  for (unsigned n = 0; n < n_var; n++)
4202  {
4203  unsigned eqn_number = elem_pt->eqn_number(n);
4204  problem_pt->dof(eqn_number) = dof_bac[n];
4205  // Pertub by vector y1
4206  problem_pt->dof(eqn_number) += y1_resolve_mult * y1_resolve[eqn_number];
4207  }
4208 
4209  // Now get the new jacobian and mass matrix
4210  elem_pt->get_jacobian_and_mass_matrix(rhs, jac_y1_resolve, M_y1_resolve);
4211 
4212  // Reset the dofs
4213  for (unsigned n = 0; n < n_var; n++)
4214  {
4215  unsigned eqn_number = elem_pt->eqn_number(n);
4216  problem_pt->dof(eqn_number) = dof_bac[n];
4217  }
4218 
4219  // OK, now work out the products
4220  for (unsigned n = 0; n < n_var; n++)
4221  {
4222  unsigned eqn_number = elem_pt->eqn_number(n);
4223  double prod_a1 = 0.0, prod_y11 = 0.0, prod_y1_resolve1 = 0.0;
4224  double prod_a2 = 0.0, prod_y12 = 0.0, prod_y1_resolve2 = 0.0;
4225  for (unsigned m = 0; m < n_var; m++)
4226  {
4227  const unsigned unknown = elem_pt->eqn_number(m);
4228  const double y = handler_pt->Phi[unknown];
4229  const double z = handler_pt->Psi[unknown];
4230  const double omega = handler_pt->Omega;
4231  // Real part (first line)
4232  prod_a1 +=
4233  (jac_a(n, m) - jac(n, m)) * y + omega * (M_a(n, m) - M(n, m)) * z;
4234  prod_y11 +=
4235  (jac_y1(n, m) - jac(n, m)) * y + omega * (M_y1(n, m) - M(n, m)) * z;
4236  prod_y1_resolve1 += (jac_y1_resolve(n, m) - jac(n, m)) * y +
4237  omega * (M_y1_resolve(n, m) - M(n, m)) * z;
4238  // Imag par (second line)
4239  prod_a2 +=
4240  (jac_a(n, m) - jac(n, m)) * z - omega * (M_a(n, m) - M(n, m)) * y;
4241  prod_y12 +=
4242  (jac_y1(n, m) - jac(n, m)) * z - omega * (M_y1(n, m) - M(n, m)) * y;
4243  prod_y1_resolve2 += (jac_y1_resolve(n, m) - jac(n, m)) * z -
4244  omega * (M_y1_resolve(n, m) - M(n, m)) * y;
4245  }
4246  Jprod_a[eqn_number] += prod_a1 / a_mult;
4247  Jprod_y1[eqn_number] += prod_y11 / y1_mult;
4248  Jprod_y1_resolve[eqn_number] += prod_y1_resolve1 / y1_resolve_mult;
4249  Jprod_a[n_dof + eqn_number] += prod_a2 / a_mult;
4250  Jprod_y1[n_dof + eqn_number] += prod_y12 / y1_mult;
4251  Jprod_y1_resolve[n_dof + eqn_number] +=
4252  prod_y1_resolve2 / y1_resolve_mult;
4253  }
4254  }
4255 
4256  // The assumption here is still that the result has been set to the
4257  // residuals.
4258  for (unsigned n = 0; n < 2 * n_dof; n++)
4259  {
4260  rhs[n] = result[n_dof + n] - Jprod_y1[n];
4261  }
4262 
4263  // Temporary storage
4264  DoubleVector y2(this->distribution_pt(), 0.0);
4265 
4266  // Solve it
4267  DoubleVector temp_rhs(this->distribution_pt(), 0.0);
4268  for (unsigned i = 0; i < 2 * n_dof; i++)
4269  {
4270  temp_rhs[i] = rhs[i];
4271  }
4272  Linear_solver_pt->resolve(temp_rhs, y2);
4273 
4274  // Assemble the next RHS
4275  for (unsigned n = 0; n < 2 * n_dof; n++)
4276  {
4277  rhs[n] = dRdparam[n_dof + n] - Jprod_a[n];
4278  }
4279 
4280  // Resive the storage
4281  if (E_pt != 0)
4282  {
4283  delete E_pt;
4284  }
4285  E_pt = new DoubleVector(this->distribution_pt(), 0.0);
4286 
4287  // Solve for the next RHS
4288  for (unsigned i = 0; i < 2 * n_dof; i++)
4289  {
4290  temp_rhs[i] = rhs[i];
4291  }
4292  Linear_solver_pt->resolve(temp_rhs, *E_pt);
4293 
4294  // Assemble the next RHS
4295  for (unsigned n = 0; n < 2 * n_dof; n++)
4296  {
4297  rhs[n] = rhs2[n_dof + n] - Jprod_y1_resolve[n];
4298  }
4299 
4300  DoubleVector y2_resolve(this->distribution_pt(), 0.0);
4301  for (unsigned i = 0; i < 2 * n_dof; i++)
4302  {
4303  temp_rhs[i] = rhs[i];
4304  }
4305  Linear_solver_pt->resolve(temp_rhs, y2_resolve);
4306 
4307 
4308  // We can now calculate the final corrections
4309  // We need to work out a large number of dot products
4310  double dot_c = 0.0, dot_d = 0.0, dot_e = 0.0, dot_f = 0.0, dot_g = 0.0;
4311  double dot_h = 0.0;
4312 
4313  double dot_c_resolve = 0.0, dot_d_resolve = 0.0;
4314 
4315  for (unsigned n = 0; n < n_dof; n++)
4316  {
4317  // Get the appopriate entry
4318  const double Cn = handler_pt->C[n];
4319  dot_c += Cn * y2[n];
4320  dot_d += Cn * y2[n_dof + n];
4321  dot_c_resolve += Cn * y2_resolve[n];
4322  dot_d_resolve += Cn * y2_resolve[n_dof + n];
4323  dot_e += Cn * (*E_pt)[n];
4324  dot_f += Cn * (*E_pt)[n_dof + n];
4325  dot_g += Cn * (*G_pt)[n];
4326  dot_h += Cn * (*G_pt)[n_dof + n];
4327  }
4328 
4329  // Now we should be able to work out the corrections
4330  double denom = dot_e * dot_h - dot_g * dot_f;
4331 
4332  // Copy the previous residuals
4333  double R31 = result[3 * n_dof], R32 = result[3 * n_dof + 1];
4334  // Delta parameter
4335  const double delta_param =
4336  ((R32 - dot_d) * dot_g - (R31 - dot_c) * dot_h) / denom;
4337  // Delta frequency
4338  const double delta_w = -((R32 - dot_d) + dot_f * delta_param) / (dot_h);
4339 
4340  // Corrections
4341  double R31_resolve = rhs2[3 * n_dof], R32_resolve = rhs2[3 * n_dof + 1];
4342  // Delta parameter
4343  const double delta_param_resolve = ((R32_resolve - dot_d_resolve) * dot_g -
4344  (R31_resolve - dot_c_resolve) * dot_h) /
4345  denom;
4346  // Delta frequency
4347  const double delta_w_resolve =
4348  -((R32_resolve - dot_d_resolve) + dot_f * delta_param_resolve) / (dot_h);
4349 
4350 
4351  // Load into the result vector
4352  result[3 * n_dof] = delta_param;
4353  result[3 * n_dof + 1] = delta_w;
4354 
4355  // The corrections to the null vector
4356  for (unsigned n = 0; n < 2 * n_dof; n++)
4357  {
4358  result[n_dof + n] =
4359  y2[n] - (*E_pt)[n] * delta_param - (*G_pt)[n] * delta_w;
4360  }
4361 
4362  // Finally add the corrections to the unknowns
4363  for (unsigned n = 0; n < n_dof; n++)
4364  {
4365  result[n] = y1[n] - (*A_pt)[n] * delta_param;
4366  }
4367 
4368  // Load into the result vector
4369  result2[3 * n_dof] = delta_param_resolve;
4370  result2[3 * n_dof + 1] = delta_w_resolve;
4371 
4372  // The corrections to the null vector
4373  for (unsigned n = 0; n < 2 * n_dof; n++)
4374  {
4375  result2[n_dof + n] = y2_resolve[n] - (*E_pt)[n] * delta_param_resolve -
4376  (*G_pt)[n] * delta_w_resolve;
4377  }
4378 
4379  // Finally add the corrections to the unknowns
4380  for (unsigned n = 0; n < n_dof; n++)
4381  {
4382  result2[n] = y1_resolve[n] - (*A_pt)[n] * delta_param_resolve;
4383  }
4384 
4385 
4386  // The sign of the jacobian is the previous signs multiplied by the
4387  // sign of the denominator
4388  problem_pt->sign_of_jacobian() =
4389  sign_of_jacobian * static_cast<int>(std::fabs(denom) / denom);
4390 
4391  // Switch things to our full solver
4392  handler_pt->solve_full_system();
4393 
4394  // If we are not storing things, clear the results
4395  if (!Enable_resolve)
4396  {
4397  // We no longer need to store the matrix
4399  delete A_pt;
4400  A_pt = 0;
4401  delete E_pt;
4402  E_pt = 0;
4403  delete G_pt;
4404  G_pt = 0;
4405  }
4406  // Otherwise, also store the problem pointer
4407  else
4408  {
4409  Problem_pt = problem_pt;
4410  }
4411  }

References A_pt, oomph::Problem::actions_after_change_in_bifurcation_parameter(), alpha, oomph::Problem::assembly_handler_pt(), oomph::DoubleVector::build(), oomph::LinearAlgebraDistribution::build(), oomph::DistributableLinearAlgebraObject::build_distribution(), oomph::DoubleVector::built(), oomph::HopfHandler::C, oomph::Problem::communicator_pt(), oomph::LinearSolver::disable_resolve(), oomph::DistributableLinearAlgebraObject::distributed(), oomph::DistributableLinearAlgebraObject::distribution_pt(), oomph::Problem::dof(), e(), E_pt, oomph::Mesh::element_pt(), oomph::LinearSolver::Enable_resolve, oomph::LinearSolver::enable_resolve(), oomph::GeneralisedElement::eqn_number(), boost::multiprecision::fabs(), oomph::BlackBoxFDNewtonSolver::FD_step, G_pt, oomph::GeneralisedElement::get_jacobian_and_mass_matrix(), oomph::Problem::get_residuals(), i, Linear_solver_pt, m, oomph::Problem::mesh_pt(), n, oomph::GeneralisedElement::ndof(), oomph::Problem::ndof(), oomph::Mesh::nelement(), Eigen::internal::omega(), oomph::HopfHandler::Omega, OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION, oomph::HopfHandler::Phi, Problem_pt, oomph::HopfHandler::Psi, oomph::LinearSolver::resolve(), oomph::Problem::sign_of_jacobian(), oomph::LinearSolver::solve(), oomph::HopfHandler::solve_complex_system(), oomph::HopfHandler::solve_full_system(), oomph::HopfHandler::solve_standard_system(), and y.

Member Data Documentation

◆ A_pt

DoubleVector* oomph::BlockHopfLinearSolver::A_pt
private

Pointer to the storage for the vector a.

Referenced by solve(), solve_for_two_rhs(), and ~BlockHopfLinearSolver().

◆ E_pt

DoubleVector* oomph::BlockHopfLinearSolver::E_pt
private

Pointer to the storage for the vector e (0 to n-1)

Referenced by solve(), solve_for_two_rhs(), and ~BlockHopfLinearSolver().

◆ G_pt

DoubleVector* oomph::BlockHopfLinearSolver::G_pt
private

Pointer to the storage for the vector g (0 to n-1)

Referenced by solve(), solve_for_two_rhs(), and ~BlockHopfLinearSolver().

◆ Linear_solver_pt

LinearSolver* oomph::BlockHopfLinearSolver::Linear_solver_pt
private

Pointer to the original linear solver.

Referenced by linear_solver_pt(), solve(), and solve_for_two_rhs().

◆ Problem_pt

Problem* oomph::BlockHopfLinearSolver::Problem_pt
private

Pointer to the problem, used in the resolve.

Referenced by solve(), and solve_for_two_rhs().


The documentation for this class was generated from the following files: