Chaste  Release::3.4
AbstractBidomainSolver.cpp
1 /*
2 
3 Copyright (c) 2005-2016, University of Oxford.
4 All rights reserved.
5 
6 University of Oxford means the Chancellor, Masters and Scholars of the
7 University of Oxford, having an administrative office at Wellington
8 Square, Oxford OX1 2JD, UK.
9 
10 This file is part of Chaste.
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions are met:
14  * Redistributions of source code must retain the above copyright notice,
15  this list of conditions and the following disclaimer.
16  * Redistributions in binary form must reproduce the above copyright notice,
17  this list of conditions and the following disclaimer in the documentation
18  and/or other materials provided with the distribution.
19  * Neither the name of the University of Oxford nor the names of its
20  contributors may be used to endorse or promote products derived from this
21  software without specific prior written permission.
22 
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 
34 */
35 
36 
37 #include "AbstractBidomainSolver.hpp"
38 #include "TetrahedralMesh.hpp"
39 #include "PetscMatTools.hpp"
40 #include "PetscVecTools.hpp"
41 
42 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
44 {
45  if (this->mpLinearSystem != NULL)
46  {
47  return;
48  }
49 
50  // linear system created here
52 
53  if (HeartConfig::Instance()->GetUseAbsoluteTolerance())
54  {
55 #ifdef TRACE_KSP
57  {
58  std::cout << "Using absolute tolerance: " << mpConfig->GetAbsoluteTolerance() <<"\n";
59  }
60 #endif
61  this->mpLinearSystem->SetAbsoluteTolerance(mpConfig->GetAbsoluteTolerance());
62  }
63  else
64  {
65 #ifdef TRACE_KSP
67  {
68  std::cout << "Using relative tolerance: " << mpConfig->GetRelativeTolerance() <<"\n";
69  }
70 #endif
71  this->mpLinearSystem->SetRelativeTolerance(mpConfig->GetRelativeTolerance());
72  }
73 
74  this->mpLinearSystem->SetKspType(HeartConfig::Instance()->GetKSPSolver());
75 
77  if(std::string("twolevelsblockdiagonal") == std::string(HeartConfig::Instance()->GetKSPPreconditioner()))
78  {
81  if (p_mesh && PetscTools::IsSequential())
82  {
83  boost::shared_ptr<std::vector<PetscInt> > p_bath_nodes(new std::vector<PetscInt>());
84 
85  for(unsigned node_index=0; node_index<this->mpMesh->GetNumNodes(); node_index++)
86  {
87  if (HeartRegionCode::IsRegionBath( this->mpMesh->GetNode(node_index)->GetRegion() ))
88  {
89  p_bath_nodes->push_back(node_index);
90  }
91  }
92 
93  this->mpLinearSystem->SetPcType(HeartConfig::Instance()->GetKSPPreconditioner(), p_bath_nodes);
94  }
95  else
96  {
97  TERMINATE("Two levels block diagonal only works with TetrahedralMesh and p=1");
98  }
99  }
100  else
101  {
102  this->mpLinearSystem->SetPcType(HeartConfig::Instance()->GetKSPPreconditioner());
103  }
104 
105  if (mRowForAverageOfPhiZeroed==INT_MAX)
106  {
107  // not applying average(phi)=0 constraint, so matrix is symmetric
108  this->mpLinearSystem->SetMatrixIsSymmetric(true);
109  }
110  else
111  {
112  //Turn off preallocation so that we can have one dense row in the matrix.
113  PetscMatTools::TurnOffVariableAllocationError(this->mpLinearSystem->rGetLhsMatrix());
114 
115  // applying average(phi)=0 constraint, so matrix is not symmetric
116  this->mpLinearSystem->SetMatrixIsSymmetric(false);
117  }
118 
119  this->mpLinearSystem->SetUseFixedNumberIterations(HeartConfig::Instance()->GetUseFixedNumberIterationsLinearSolver(), HeartConfig::Instance()->GetEvaluateNumItsEveryNSolves());
120 }
121 
122 
123 
124 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
126 {
127  mpBidomainTissue->SolveCellSystems(existingSolution, PdeSimulationTime::GetTime(), PdeSimulationTime::GetNextTime());
128 }
129 
130 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
132 {
133  double sqrt_num_nodes = sqrt((double) this->mpMesh->GetNumNodes());
134 
135  Vec null_basis;
136  DistributedVectorFactory* p_factory = this->mpMesh->GetDistributedVectorFactory();
137  null_basis = p_factory->CreateVec(2);
138 
139  DistributedVector dist_null_basis = p_factory->CreateDistributedVector(null_basis);
140  DistributedVector::Stripe null_basis_stripe_0(dist_null_basis,0);
141  DistributedVector::Stripe null_basis_stripe_1(dist_null_basis,1);
142  for (DistributedVector::Iterator index = dist_null_basis.Begin();
143  index != dist_null_basis.End();
144  ++index)
145  {
146  null_basis_stripe_0[index] = 0.0;
147  null_basis_stripe_1[index] = 1.0/sqrt_num_nodes; // normalised vector
148  }
149  dist_null_basis.Restore();
150 
151  return null_basis;
152 }
153 
154 
155 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
157 {
158  // If no dirichlet boundary conditions
159  // (i) Check compatibility condition to check we are solving
160  // a linear system that can be solved
161  // Then either
162  // (a) If not setting average(phi)=0, we are solving a singular system,
163  // so set up a null space
164  // (b) Apply average(phi)=0 constraint by altering the last row, to
165  // get a non-singular system
166  //
167  if (!(GetBoundaryConditions()->HasDirichletBoundaryConditions()))
168  {
169  // first check compatibility condition
170  CheckCompatibilityCondition();
171 
172  // Check whether applying average(phi_e)=0 constraint
173  if (mRowForAverageOfPhiZeroed==INT_MAX)
174  {
175  // We're not using the constraint, hence use a null space
176  if (!mNullSpaceCreated)
177  {
178  // No null space set up, so create one and pass it to the linear system
179  Vec null_basis[] = {GenerateNullBasis()};
180 
181  this->mpLinearSystem->SetNullBasis(null_basis, 1);
182 
183  PetscTools::Destroy(null_basis[0]);
184  mNullSpaceCreated = true;
185  }
186  }
187  else
188  {
189  // Using the average(phi_e)=0 constraint
190 
191  // CG (default solver) won't work since the system isn't symmetric anymore. Switch to GMRES
192  this->mpLinearSystem->SetKspType("gmres"); // Switches the solver
193  mpConfig->SetKSPSolver("gmres", true); // Makes sure this change will be reflected in the XML file written to disk at the end of the simulation.
194  //(If the user doesn't have gmres then the "true" warns the user about the switch)
195 
196  // Set average phi_e to zero
197  unsigned matrix_size = this->mpLinearSystem->GetSize();
198  if (!this->mMatrixIsAssembled)
199  {
200 
201  // Set the mRowForAverageOfPhiZeroed-th matrix row to 0 1 0 1 ...
202  std::vector<unsigned> row_for_average;
203  row_for_average.push_back(mRowForAverageOfPhiZeroed);
204  this->mpLinearSystem->ZeroMatrixRowsWithValueOnDiagonal(row_for_average, 0.0);
205  for (unsigned col_index=0; col_index<matrix_size; col_index++)
206  {
207  if (col_index%2 == 1)
208  {
209  this->mpLinearSystem->SetMatrixElement(mRowForAverageOfPhiZeroed, col_index, 1);
210  }
211 
212  }
213  this->mpLinearSystem->FinaliseLhsMatrix();
214 
215  }
216  // Set the mRowForAverageOfPhiZeroed-th rhs vector row to 0
217  this->mpLinearSystem->SetRhsVectorElement(mRowForAverageOfPhiZeroed, 0);
218 
219  this->mpLinearSystem->FinaliseRhsVector();
220  }
221  }
222 }
223 
224 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
226 {
227  if (GetBoundaryConditions()->HasDirichletBoundaryConditions() || this->mRowForAverageOfPhiZeroed!=INT_MAX)
228  {
229  // not a singular system, no compatibility condition
230  return;
231  }
232 
233 #ifndef NDEBUG
234  DistributedVector distributed_rhs=this->mpMesh->GetDistributedVectorFactory()->CreateDistributedVector(this->mpLinearSystem->rGetRhsVector());
235  DistributedVector::Stripe distributed_rhs_phi_entries(distributed_rhs,1);
236 
237  double local_sum=0;
238  for (DistributedVector::Iterator index = distributed_rhs.Begin();
239  index!= distributed_rhs.End();
240  ++index)
241  {
242  local_sum += distributed_rhs_phi_entries[index];
243  }
244 
245  double global_sum;
246  int mpi_ret = MPI_Allreduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, PETSC_COMM_WORLD);
247  assert(mpi_ret == MPI_SUCCESS);
248 
249  if(fabs(global_sum)>1e-6) // magic number! sum should really be a sum of zeros and exactly zero though anyway (or a-a+b-b+c-c.. etc in the case of electrodes)
250  {
251  #define COVERAGE_IGNORE
252  // shouldn't ever reach this line but useful to have the error printed out if you do
253  //std::cout << "Sum of b_{2i+1} = " << global_sum << " (should be zero for compatibility)\n";
254  EXCEPTION("Linear system does not satisfy compatibility constraint!");
255  #undef COVERAGE_IGNORE
256  }
257 #endif
258 }
259 
260 
261 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
263  bool bathSimulation,
265  BidomainTissue<SPACE_DIM>* pTissue,
267  : AbstractDynamicLinearPdeSolver<ELEMENT_DIM,SPACE_DIM,2>(pMesh),
268  mBathSimulation(bathSimulation),
269  mpBidomainTissue(pTissue),
270  mpBoundaryConditions(pBoundaryConditions)
271 {
272  assert(pTissue != NULL);
273  assert(pBoundaryConditions != NULL);
274 
275  mNullSpaceCreated = false;
276 
277  // important!
278  this->mMatrixIsConstant = true;
279 
280  mRowForAverageOfPhiZeroed = INT_MAX; //this->mpLinearSystem->GetSize() - 1;
282 }
283 
284 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
286 {
287 }
288 
289 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
291  std::vector<unsigned> fixedExtracellularPotentialNodes)
292 {
293  for (unsigned i=0; i<fixedExtracellularPotentialNodes.size(); i++)
294  {
295  if (fixedExtracellularPotentialNodes[i] >= this->mpMesh->GetNumNodes() )
296  {
297  EXCEPTION("Fixed node number must be less than total number nodes");
298  }
299  }
300 
301  mFixedExtracellularPotentialNodes = fixedExtracellularPotentialNodes;
302 
303  // We will need to recalculate this when HasDirichletBoundaryConditions() is called.
304  GetBoundaryConditions()->ResetDirichletCommunication();
305 
306  for (unsigned i=0; i<mFixedExtracellularPotentialNodes.size(); i++)
307  {
308  if (this->mpMesh->GetDistributedVectorFactory()->IsGlobalIndexLocal(mFixedExtracellularPotentialNodes[i]))
309  {
310  ConstBoundaryCondition<SPACE_DIM>* p_boundary_condition
312 
313  //Throws if node is not owned locally
314  Node<SPACE_DIM>* p_node = this->mpMesh->GetNode(mFixedExtracellularPotentialNodes[i]);
315 
316  GetBoundaryConditions()->AddDirichletBoundaryCondition(p_node, p_boundary_condition, 1);
317 
318  }
319  }
320 }
321 
322 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
324 {
325  // Row should be odd in C++-like indexing
326  if (row%2 == 0)
327  {
328  EXCEPTION("Row for applying the constraint 'Average of phi_e = zero' should be odd in C++ like indexing");
329  }
330 
331  mRowForAverageOfPhiZeroed = row;
332 }
333 
334 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
335 void AbstractBidomainSolver<ELEMENT_DIM,SPACE_DIM>::FinaliseForBath(bool computeMatrix, bool computeVector)
336 {
337  assert(mBathSimulation);
338  PetscBool is_matrix_assembled;
339  MatAssembled(this->mpLinearSystem->GetLhsMatrix(), &is_matrix_assembled);
340  assert(is_matrix_assembled == PETSC_TRUE);
341 
342  /*
343  * Before revision 6516, we used to zero out i-th row and column here. It seems to be redundant because they are already zero after assembly.
344  * When assembling a bath element you get a matrix subblock that looks like (2D example):
345  *
346  * Vm 0 0 0 0 0 0
347  * Vm 0 0 0 0 0 0
348  * Vm 0 0 0 0 0 0
349  * Phie 0 0 0 x x x
350  * Phie 0 0 0 x x x -> the x subblock is assembled from div(grad_phi) = 0
351  * Phie 0 0 0 x x x
352  *
353  * Therefore, all the Vm entries of this node are already 0.
354  *
355  * Explicitly checking it in non-production builds.
356  */
357 #ifndef NDEBUG
358  if(computeMatrix)
359  {
360  for (typename AbstractTetrahedralMesh<ELEMENT_DIM,SPACE_DIM>::NodeIterator iter=this->mpMesh->GetNodeIteratorBegin();
361  iter != this->mpMesh->GetNodeIteratorEnd();
362  ++iter)
363  {
364  if (HeartRegionCode::IsRegionBath( (*iter).GetRegion() ))
365  {
366  int num_equation = 2*iter->GetIndex(); // assumes Vm and Phie are interleaved
367 
368  PetscInt local_lo, local_hi;
369  this->mpLinearSystem->GetOwnershipRange(local_lo, local_hi);
370 
371  // If this processor owns i-th row, check it.
372  if ((local_lo <= (int)num_equation) && ((int)num_equation < local_hi))
373  {
374  for (unsigned column=0; column < this->mpLinearSystem->GetSize(); column++)
375  {
376  assert(this->mpLinearSystem->GetMatrixElement(num_equation, column)==0.0);
377  }
378  }
379 
380  // Check the local entries of the i-th column
381  for (int row=local_lo; row<local_hi; row++)
382  {
383  assert(this->mpLinearSystem->GetMatrixElement(row, num_equation)==0);
384  }
385  }
386  }
387  }
388 #endif
389 
390  /*
391  * These two loops are decoupled because interleaving calls to GetMatrixElement and MatSetValue
392  * require reassembling the matrix before GetMatrixElement which generates massive communication
393  * overhead for large models and/or large core counts.
394  */
395 
396  for (typename AbstractTetrahedralMesh<ELEMENT_DIM,SPACE_DIM>::NodeIterator iter=this->mpMesh->GetNodeIteratorBegin();
397  iter != this->mpMesh->GetNodeIteratorEnd();
398  ++iter)
399  {
400  if (HeartRegionCode::IsRegionBath((*iter).GetRegion() ))
401  {
402  PetscInt index = 2*iter->GetIndex(); // assumes Vm and Phie are interleaved
403 
404  if(computeMatrix)
405  {
406  // put 1.0 on the diagonal
407  PetscMatTools::SetElement(this->mpLinearSystem->rGetLhsMatrix(), index, index, 1.0);
408  }
409 
410  if(computeVector)
411  {
412  // zero rhs vector entry
413  PetscVecTools::SetElement(this->mpLinearSystem->rGetRhsVector(), index, 0.0);
414  }
415  }
416  }
417 }
418 
420 // explicit instantiation
422 
423 template class AbstractBidomainSolver<1,1>;
424 template class AbstractBidomainSolver<2,2>;
425 template class AbstractBidomainSolver<3,3>;
static bool IsRegionBath(HeartRegionType regionId)
virtual void CheckCompatibilityCondition()
Definition: Node.hpp:58
virtual void InitialiseForSolve(Vec initialSolution=NULL)
void InitialiseForSolve(Vec initialSolution)
static void TurnOffVariableAllocationError(Mat matrix)
virtual void FinaliseLinearSystem(Vec existingSolution)
DistributedVector CreateDistributedVector(Vec vec, bool readOnly=false)
#define EXCEPTION(message)
Definition: Exception.hpp:143
static void SetElement(Vec vector, PetscInt row, double value)
static bool AmMaster()
Definition: PetscTools.cpp:120
#define TERMINATE(message)
Definition: Exception.hpp:168
void SetFixedExtracellularPotentialNodes(std::vector< unsigned > fixedExtracellularPotentialNodes)
static bool IsSequential()
Definition: PetscTools.cpp:91
void FinaliseForBath(bool computeMatrix, bool computeVector)
static double GetNextTime()
static void SetElement(Mat matrix, PetscInt row, PetscInt col, double value)
static void Destroy(Vec &rVec)
Definition: PetscTools.hpp:351
void PrepareForSetupLinearSystem(Vec existingSolution)
AbstractBidomainSolver(bool bathSimulation, AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > *pMesh, BidomainTissue< SPACE_DIM > *pTissue, BoundaryConditionsContainer< ELEMENT_DIM, SPACE_DIM, 2 > *pBoundaryConditions)
void SetRowForAverageOfPhiZeroed(unsigned rowMeanPhiEZero)
static HeartConfig * Instance()
virtual Vec GenerateNullBasis() const
static double GetTime()