Chaste Commit::675f9facbe008c5eacb9006feaeb6423206579ea
CardiacElectroMechanicsProblem.cpp
1/*
2
3Copyright (c) 2005-2025, University of Oxford.
4All rights reserved.
5
6University of Oxford means the Chancellor, Masters and Scholars of the
7University of Oxford, having an administrative office at Wellington
8Square, Oxford OX1 2JD, UK.
9
10This file is part of Chaste.
11
12Redistribution and use in source and binary forms, with or without
13modification, 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
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34*/
35
36#include "CardiacElectroMechanicsProblem.hpp"
37
38#include "OutputFileHandler.hpp"
39#include "ReplicatableVector.hpp"
40#include "HeartConfig.hpp"
41#include "LogFile.hpp"
42#include "ChastePoint.hpp"
43#include "Element.hpp"
44#include "BoundaryConditionsContainer.hpp"
45#include "AbstractDynamicLinearPdeSolver.hpp"
46#include "TimeStepper.hpp"
47#include "TrianglesMeshWriter.hpp"
48#include "Hdf5ToMeshalyzerConverter.hpp"
49#include "Hdf5ToCmguiConverter.hpp"
50#include "MeshalyzerMeshWriter.hpp"
51#include "PetscTools.hpp"
52#include "ImplicitCardiacMechanicsSolver.hpp"
53#include "ExplicitCardiacMechanicsSolver.hpp"
54#include "CmguiDeformedSolutionsWriter.hpp"
55#include "VoltageInterpolaterOntoMechanicsMesh.hpp"
56#include "BidomainProblem.hpp"
57
58
59template<unsigned DIM, unsigned ELEC_PROB_DIM>
61{
62 assert(mIsWatchedLocation);
63
64 // find the nearest electrics mesh node
65 double min_dist = DBL_MAX;
66 unsigned node_index = UNSIGNED_UNSET;
67 for (unsigned i=0; i<mpElectricsMesh->GetNumNodes(); i++)
68 {
69 double dist = norm_2(mWatchedLocation - mpElectricsMesh->GetNode(i)->rGetLocation());
70 if (dist < min_dist)
71 {
72 min_dist = dist;
73 node_index = i;
74 }
75 }
76
77 // set up watched node, if close enough
78 assert(node_index != UNSIGNED_UNSET); // should def have found something
79 c_vector<double,DIM> pos = mpElectricsMesh->GetNode(node_index)->rGetLocation();
80
81 if (min_dist > 1e-8)
82 {
83 // LCOV_EXCL_START
84 std::cout << "ERROR: Could not find an electrics node very close to requested watched location - "
85 << "min distance was " << min_dist << " for node " << node_index
86 << " at location " << pos << std::flush;;
87
89 //EXCEPTION("Could not find an electrics node very close to requested watched location");
91 // LCOV_EXCL_STOP
92 }
93 else
94 {
95 LOG(2,"Chosen electrics node "<<node_index<<" at location " << pos << " to be watched");
96 mWatchedElectricsNodeIndex = node_index;
97 }
98
99 // find nearest mechanics mesh
100 min_dist = DBL_MAX;
101 node_index = UNSIGNED_UNSET;
102 c_vector<double,DIM> pos_at_min;
103
104 for (unsigned i=0; i<mpMechanicsMesh->GetNumNodes(); i++)
105 {
106 c_vector<double,DIM> position = mpMechanicsMesh->GetNode(i)->rGetLocation();
107
108 double dist = norm_2(position-mWatchedLocation);
109
110 if (dist < min_dist)
111 {
112 min_dist = dist;
113 node_index = i;
114 pos_at_min = position;
115 }
116 }
117
118 // set up watched node, if close enough
119 assert(node_index != UNSIGNED_UNSET); // should def have found something
120
121 if (min_dist > 1e-8)
122 {
123 // LCOV_EXCL_START
124 std::cout << "ERROR: Could not find a mechanics node very close to requested watched location - "
125 << "min distance was " << min_dist << " for node " << node_index
126 << " at location " << pos_at_min;
127
129 //EXCEPTION("Could not find a mechanics node very close to requested watched location");
131 // LCOV_EXCL_STOP
132 }
133 else
134 {
135 LOG(2,"Chosen mechanics node "<<node_index<<" at location " << pos << " to be watched");
136 mWatchedMechanicsNodeIndex = node_index;
137 }
138
139 OutputFileHandler handler(mOutputDirectory,false);
140 mpWatchedLocationFile = handler.OpenOutputFile("watched.txt");
141}
142
143template<unsigned DIM, unsigned ELEC_PROB_DIM>
145{
146 assert(mIsWatchedLocation);
147
148 std::vector<c_vector<double,DIM> >& deformed_position = mpMechanicsSolver->rGetDeformedPosition();
149
151 ReplicatableVector voltage_replicated(voltage);
152 double V = voltage_replicated[mWatchedElectricsNodeIndex];
153
156 // // Metadata is currently being added to CellML models and then this will be avoided by asking for Calcium.
157 // double Ca = mpElectricsProblem->GetMonodomainTissue()->GetCardiacCell(mWatchedElectricsNodeIndex)->GetIntracellularCalciumConcentration();
158
159 *mpWatchedLocationFile << time << " ";
160 for (unsigned i=0; i<DIM; i++)
161 {
162 *mpWatchedLocationFile << deformed_position[mWatchedMechanicsNodeIndex](i) << " ";
163 }
164 *mpWatchedLocationFile << V << "\n";
165 mpWatchedLocationFile->flush();
166}
167
168template<unsigned DIM, unsigned ELEC_PROB_DIM>
169c_matrix<double,DIM,DIM>& CardiacElectroMechanicsProblem<DIM,ELEC_PROB_DIM>::rCalculateModifiedConductivityTensor(unsigned elementIndex, const c_matrix<double,DIM,DIM>& rOriginalConductivity, unsigned domainIndex)
170{
171
172 // first get the deformation gradient for this electrics element
173 unsigned containing_mechanics_elem = mpMeshPair->rGetCoarseElementsForFineElementCentroids()[elementIndex];
174 c_matrix<double,DIM,DIM>& r_deformation_gradient = mDeformationGradientsForEachMechanicsElement[containing_mechanics_elem];
175
176 // compute sigma_def = F^{-1} sigma_undef F^{-T}
177 c_matrix<double,DIM,DIM> inv_F = Inverse(r_deformation_gradient);
178 mModifiedConductivityTensor = prod(inv_F, rOriginalConductivity);
179 mModifiedConductivityTensor = prod(mModifiedConductivityTensor, trans(inv_F));
180
181 return mModifiedConductivityTensor;
182}
183
184
188template<unsigned DIM, unsigned PROBLEM_DIM>
190{
191public:
198 static AbstractCardiacProblem<DIM, DIM, PROBLEM_DIM>* Create(ElectricsProblemType problemType,
199 AbstractCardiacCellFactory<DIM>* pCellFactory);
200};
201
205template<unsigned DIM>
207{
208public:
215 static AbstractCardiacProblem<DIM, DIM, 1u>* Create(ElectricsProblemType problemType,
217 {
218 if (problemType == MONODOMAIN)
219 {
220 return new MonodomainProblem<DIM>(pCellFactory);
221 }
222 EXCEPTION("The second template parameter should be 2 when a bidomain problem is chosen");
223 }
224};
225
229template<unsigned DIM>
231{
232public:
239 static AbstractCardiacProblem<DIM, DIM, 2u>* Create(ElectricsProblemType problemType,
241 {
242 if (problemType == BIDOMAIN)
243 {
244 return new BidomainProblem<DIM>(pCellFactory, false);//false-> no bath
245 }
246 if (problemType == BIDOMAIN_WITH_BATH)
247 {
248 return new BidomainProblem<DIM>(pCellFactory, true);// true-> bath
249 }
250 EXCEPTION("The second template parameter should be 1 when a monodomain problem is chosen");
251 }
252};
253
254
255template<unsigned DIM, unsigned ELEC_PROB_DIM>
257 CompressibilityType compressibilityType,
258 ElectricsProblemType electricsProblemType,
259 TetrahedralMesh<DIM,DIM>* pElectricsMesh,
260 QuadraticMesh<DIM>* pMechanicsMesh,
262 ElectroMechanicsProblemDefinition<DIM>* pProblemDefinition,
263 std::string outputDirectory)
264 : mCompressibilityType(compressibilityType),
265 mpCardiacMechSolver(NULL),
266 mpMechanicsSolver(NULL),
267 mpElectricsMesh(pElectricsMesh),
268 mpMechanicsMesh(pMechanicsMesh),
269 mpProblemDefinition(pProblemDefinition),
270 mHasBath(false),
271 mpMeshPair(NULL),
272 mNoElectricsOutput(false),
273 mIsWatchedLocation(false),
274 mWatchedElectricsNodeIndex(UNSIGNED_UNSET),
275 mWatchedMechanicsNodeIndex(UNSIGNED_UNSET),
276 mNumTimestepsToOutputDeformationGradientsAndStress(UNSIGNED_UNSET),
277 mpCardiacVtkWriter(NULL)
278{
279 // Do some initial set up...
280 // However, NOTE, we don't use either the passed in meshes or the problem_definition.
281 // These pointers are allowed to be NULL, in case a child constructor wants to set
282 // them up (eg CardiacElectroMechProbRegularGeom).
283 // The meshes and problem_defn are used for the first time in Initialise().
284
285
286 // Start-up mechanics event handler..
288 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::ALL);
289 // disable the electric event handler, because we use a problem class but
290 // don't call Solve, so we would have to worry about starting and ending any
291 // events in AbstractCardiacProblem::Solve() (esp. calling EndEvent(EVERYTHING))
292 // if we didn't disable it.
294
295 assert(HeartConfig::Instance()->GetSimulationDuration()>0.0);
296 assert(HeartConfig::Instance()->GetPdeTimeStep()>0.0);
297
298 // Create the monodomain problem.
299 // **NOTE** WE ONLY USE THIS TO: set up the cells, get an initial condition
300 // (voltage) vector, and get a solver. We won't ever call Solve on the cardiac problem class
301 assert(pCellFactory != NULL);
302 mpElectricsProblem = CreateElectricsProblem<DIM,ELEC_PROB_DIM>::Create(electricsProblemType, pCellFactory);
303
304 if (electricsProblemType == BIDOMAIN_WITH_BATH)
305 {
306 mHasBath = true;
307 }
308 // check whether output is required
309 mWriteOutput = (outputDirectory!="");
310 if (mWriteOutput)
311 {
312 mOutputDirectory = outputDirectory;
313 // create the directory
318 }
319 else
320 {
322 }
323
324// mpImpactRegion=NULL;
325}
326
327template<unsigned DIM, unsigned ELEC_PROB_DIM>
329{
330 // NOTE if SetWatchedLocation but not Initialise has been called, mpWatchedLocationFile
331 // will be uninitialised and using it will cause a seg fault. Hence the mpMechanicsMesh!=NULL
332 // it is true if Initialise has been called.
333 if (mIsWatchedLocation && mpMechanicsMesh)
334 {
335 mpWatchedLocationFile->close();
336 }
337
338 delete mpElectricsProblem;
339 delete mpCardiacMechSolver;
340 delete mpMeshPair;
341 delete mpCardiacVtkWriter;
342
344}
345
346template<unsigned DIM, unsigned ELEC_PROB_DIM>
348{
349 assert(mpElectricsMesh!=NULL);
350 assert(mpMechanicsMesh!=NULL);
351 assert(mpProblemDefinition!=NULL);
352 assert(mpCardiacMechSolver==NULL);
353
356 mNumElecTimestepsPerMechTimestep = (unsigned) floor((mpProblemDefinition->GetMechanicsSolveTimestep()/HeartConfig::Instance()->GetPdeTimeStep())+0.5);
357 if (fabs(mNumElecTimestepsPerMechTimestep*HeartConfig::Instance()->GetPdeTimeStep() - mpProblemDefinition->GetMechanicsSolveTimestep()) > 1e-6)
358 {
359 EXCEPTION("Electrics PDE timestep does not divide mechanics solve timestep");
360 }
361
362 // Create the Logfile (note we have to do this after the output dir has been
363 // created, else the log file might get cleaned away
364 std::string log_dir = mOutputDirectory; // just the TESTOUTPUT dir if mOutputDir="";
365 LogFile::Instance()->Set(2, mOutputDirectory);
366 LogFile::Instance()->WriteHeader("Electromechanics");
367 LOG(2, DIM << "d Implicit CardiacElectroMechanics Simulation:");
368 LOG(2, "End time = " << HeartConfig::Instance()->GetSimulationDuration() << ", electrics time step = " << HeartConfig::Instance()->GetPdeTimeStep() << ", mechanics timestep = " << mpProblemDefinition->GetMechanicsSolveTimestep() << "\n");
369 LOG(2, "Contraction model ode timestep " << mpProblemDefinition->GetContractionModelOdeTimestep());
370 LOG(2, "Output is written to " << mOutputDirectory << "/[deformation/electrics]");
371
372 LOG(2, "Electrics mesh has " << mpElectricsMesh->GetNumNodes() << " nodes");
373 LOG(2, "Mechanics mesh has " << mpMechanicsMesh->GetNumNodes() << " nodes");
374
375 LOG(2, "Initialising..");
376
377
378 if (mIsWatchedLocation)
379 {
380 DetermineWatchedNodes();
381 }
382
383 // initialise electrics problem
384 mpElectricsProblem->SetMesh(mpElectricsMesh);
385 mpElectricsProblem->Initialise();
386
387 if (mCompressibilityType==INCOMPRESSIBLE)
388 {
389 switch(mpProblemDefinition->GetSolverType())
390 {
391 case EXPLICIT:
393 *mpMechanicsMesh,*mpProblemDefinition,mDeformationOutputDirectory);
394 break;
395 case IMPLICIT:
397 *mpMechanicsMesh,*mpProblemDefinition,mDeformationOutputDirectory);
398 break;
399 default:
401 }
402 }
403 else
404 {
405 // repeat above with Compressible solver rather than incompressible -
406 // not the neatest but avoids having to template this class.
407 switch(mpProblemDefinition->GetSolverType())
408 {
409 case EXPLICIT:
411 *mpMechanicsMesh,*mpProblemDefinition,mDeformationOutputDirectory);
412 break;
413 case IMPLICIT:
415 *mpMechanicsMesh,*mpProblemDefinition,mDeformationOutputDirectory);
416 break;
417 default:
419 }
420 }
421
422
423 mpMechanicsSolver = dynamic_cast<AbstractNonlinearElasticitySolver<DIM>*>(mpCardiacMechSolver);
424 assert(mpMechanicsSolver);
425
426 // set up mesh pair and determine the fine mesh elements and corresponding weights for each
427 // quadrature point in the coarse mesh
428 mpMeshPair = new FineCoarseMeshPair<DIM>(*mpElectricsMesh, *mpMechanicsMesh);
429 mpMeshPair->SetUpBoxesOnFineMesh();
430 mpMeshPair->ComputeFineElementsAndWeightsForCoarseQuadPoints(*(mpCardiacMechSolver->GetQuadratureRule()), false);
431 mpMeshPair->DeleteFineBoxCollection();
432
433 mpCardiacMechSolver->SetFineCoarseMeshPair(mpMeshPair);
434 mpCardiacMechSolver->Initialise();
435
436 unsigned num_quad_points = mpCardiacMechSolver->GetTotalNumQuadPoints();
437 mInterpolatedCalciumConcs.assign(num_quad_points, 0.0);
438 mInterpolatedVoltages.assign(num_quad_points, 0.0);
439
440 if (mpProblemDefinition->ReadFibreSheetDirectionsFromFile())
441 {
442 mpCardiacMechSolver->SetVariableFibreSheetDirections(mpProblemDefinition->GetFibreSheetDirectionsFile(),
443 mpProblemDefinition->GetFibreSheetDirectionsDefinedPerQuadraturePoint());
444 }
445
446
447 if (mpProblemDefinition->GetDeformationAffectsConductivity() || mpProblemDefinition->GetDeformationAffectsCellModels())
448 {
449 mpMeshPair->SetUpBoxesOnCoarseMesh();
450 }
451
452
453 if (mpProblemDefinition->GetDeformationAffectsCellModels() || mpProblemDefinition->GetDeformationAffectsConductivity())
454 {
455 // initialise the stretches saved for each mechanics element
456 mStretchesForEachMechanicsElement.resize(mpMechanicsMesh->GetNumElements(), 1.0);
457
458 // initialise the store of the F in each mechanics element (one constant value of F) in each
459 mDeformationGradientsForEachMechanicsElement.resize(mpMechanicsMesh->GetNumElements(),identity_matrix<double>(DIM));
460 }
461
462
463 if (mpProblemDefinition->GetDeformationAffectsCellModels())
464 {
465 // compute the coarse elements which contain each fine node -- for transferring stretch from
466 // mechanics solve electrics cell models
467 mpMeshPair->ComputeCoarseElementsForFineNodes(false);
468
469 }
470
471 if (mpProblemDefinition->GetDeformationAffectsConductivity())
472 {
473 // compute the coarse elements which contain each fine element centroid -- for transferring F from
474 // mechanics solve to electrics mesh elements
475 mpMeshPair->ComputeCoarseElementsForFineElementCentroids(false);
476
477 // tell the abstract tissue class that the conductivities need to be modified, passing in this class
478 // (which is of type AbstractConductivityModifier)
479 mpElectricsProblem->GetTissue()->SetConductivityModifier(this);
480 }
481
482 if (mWriteOutput)
483 {
484 TrianglesMeshWriter<DIM,DIM> mesh_writer(mOutputDirectory,"electrics_mesh",false);
485 mesh_writer.WriteFilesUsingMesh(*mpElectricsMesh);
486 }
487}
488
489template<unsigned DIM, unsigned ELEC_PROB_DIM>
491{
492 // initialise the meshes and mechanics solver
493 if (mpCardiacMechSolver==NULL)
494 {
495 Initialise();
496 }
497
498 bool verbose_during_solve = ( mpProblemDefinition->GetVerboseDuringSolve()
499 || CommandLineArguments::Instance()->OptionExists("-mech_verbose")
500 || CommandLineArguments::Instance()->OptionExists("-mech_very_verbose"));
501
502
503 mpProblemDefinition->Validate();
504
505 boost::shared_ptr<BoundaryConditionsContainer<DIM,DIM,ELEC_PROB_DIM> > p_bcc(new BoundaryConditionsContainer<DIM,DIM,ELEC_PROB_DIM>);
506 p_bcc->DefineZeroNeumannOnMeshBoundary(mpElectricsMesh, 0);
507 mpElectricsProblem->SetBoundaryConditionsContainer(p_bcc);
508
509 // get an electrics solver from the problem. Note that we don't call
510 // Solve() on the CardiacProblem class, we do the looping here.
512 = mpElectricsProblem->CreateSolver();
513
514 // set up initial voltage etc
515 Vec electrics_solution=NULL; //This will be set and used later
516 Vec calcium_data= mpElectricsMesh->GetDistributedVectorFactory()->CreateVec();
517 Vec initial_voltage = mpElectricsProblem->CreateInitialCondition();
518
519 // write the initial position
520 unsigned counter = 0;
521
522 TimeStepper stepper(0.0, HeartConfig::Instance()->GetSimulationDuration(), mpProblemDefinition->GetMechanicsSolveTimestep());
523
524 CmguiDeformedSolutionsWriter<DIM>* p_cmgui_writer = NULL;
525
526 std::vector<std::string> variable_names;
527
528 if (mWriteOutput)
529 {
530#ifdef CHASTE_VTK
531 if (HeartConfig::Instance()->GetVisualizeWithVtk())
532 {
533 ReplicatableVector ic(initial_voltage);
534 mpCardiacVtkWriter = new CardiacElectroMechanicsVtkHandler<DIM,ELEC_PROB_DIM>(*mpMechanicsSolver,
535 *mpMechanicsMesh,*mpElectricsMesh, ic, mDeformationOutputDirectory);
536 }
537#endif
538 mpMechanicsSolver->SetWriteOutput();
539 mpMechanicsSolver->WriteCurrentSpatialSolution("undeformed","nodes");
540
541 p_cmgui_writer = new CmguiDeformedSolutionsWriter<DIM>(mOutputDirectory+"/deformation/cmgui",
542 "solution",
543 *(this->mpMechanicsMesh),
544 WRITE_QUADRATIC_MESH);
545
546 variable_names.push_back("V");
547 if (ELEC_PROB_DIM==2)
548 {
549 variable_names.push_back("Phi_e");
550 if (mHasBath==true)
551 {
552 std::vector<std::string> regions;
553 regions.push_back("tissue");
554 regions.push_back("bath");
555 p_cmgui_writer->SetRegionNames(regions);
556 }
557 }
558 p_cmgui_writer->SetAdditionalFieldNames(variable_names);
559 p_cmgui_writer->WriteInitialMesh("undeformed");
560 }
561
568
569 LOG(2, "\nSolving for initial deformation");
570 // LCOV_EXCL_START
571 if (verbose_during_solve)
572 {
573 std::cout << "\n\n ** Solving for initial deformation\n";
574 }
575 // LCOV_EXCL_STOP
576
577 mpMechanicsSolver->SetWriteOutput(false);
578
579 mpMechanicsSolver->SetCurrentTime(0.0);
580 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::ALL_MECH);
581
582 mpMechanicsSolver->SetIncludeActiveTension(false);
583 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET)
584 {
585 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(true);
586 }
587
588 unsigned total_newton_iters = 0;
589 for (unsigned index=1; index<=mpProblemDefinition->GetNumIncrementsForInitialDeformation(); index++)
590 {
591 // LCOV_EXCL_START
592 if (verbose_during_solve)
593 {
594 std::cout << " Increment " << index << " of " << mpProblemDefinition->GetNumIncrementsForInitialDeformation() << "\n";
595 }
596 // LCOV_EXCL_STOP
597
598 if (mpProblemDefinition->GetTractionBoundaryConditionType()==PRESSURE_ON_DEFORMED)
599 {
600 mpProblemDefinition->SetPressureScaling(((double)index)/mpProblemDefinition->GetNumIncrementsForInitialDeformation());
601 }
602 mpMechanicsSolver->Solve();
603
604 total_newton_iters += mpMechanicsSolver->GetNumNewtonIterations();
605 }
606
607 mpMechanicsSolver->SetIncludeActiveTension(true);
608 MechanicsEventHandler::EndEvent(MechanicsEventHandler::ALL_MECH);
609 LOG(2, " Number of newton iterations = " << total_newton_iters);
610
611
612 unsigned mech_writer_counter = 0;
613
614 if (mWriteOutput)
615 {
616 LOG(2, " Writing output");
617 mpMechanicsSolver->SetWriteOutput();
618 mpMechanicsSolver->WriteCurrentSpatialSolution("solution","nodes",mech_writer_counter);
619 p_cmgui_writer->WriteDeformationPositions(rGetDeformedPosition(), mech_writer_counter);
620
621 if (!mNoElectricsOutput)
622 {
623 // the writer inside monodomain problem uses the printing timestep
624 // inside HeartConfig to estimate total number of timesteps, so make
625 // sure this is set to what we will use.
626 HeartConfig::Instance()->SetPrintingTimeStep(mpProblemDefinition->GetMechanicsSolveTimestep());
627
628 // When we consider archiving these simulations we will need to get a bool back from the following
629 // command to decide whether or not the file is being extended, and hence whether to write the
630 // initial conditions into the .h5 file.
631 mpElectricsProblem->InitialiseWriter();
632 mpElectricsProblem->WriteOneStep(stepper.GetTime(), initial_voltage);
633 }
634
635 if (mIsWatchedLocation)
636 {
637 WriteWatchedLocationData(stepper.GetTime(), initial_voltage);
638 }
639
640 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET)
641 {
642 mpMechanicsSolver->WriteCurrentStrains(DEFORMATION_GRADIENT_F,"deformation_gradient",mech_writer_counter);
643 mpMechanicsSolver->WriteCurrentAverageElementStresses("second_PK",mech_writer_counter);
644 }
645 }
646
647
648 PrepareForSolve();
649
653// std::vector<double> current_solution_previous_time_step = mpMechanicsSolver->rGetCurrentSolution();
654// std::vector<double> current_solution_second_last_time_step = mpMechanicsSolver->rGetCurrentSolution();
655// bool first_step = true;
656
657
658 // reset this to false, may be reset again below
659 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(false);
660
661 while (!stepper.IsTimeAtEnd())
662 {
663 LOG(2, "\nCurrent time = " << stepper.GetTime());
664 // LCOV_EXCL_START
665 if (verbose_during_solve)
666 {
667 // also output time to screen as newton solve information will be output
668 std::cout << "\n\n ** Current time = " << stepper.GetTime() << "\n";
669 }
670 // LCOV_EXCL_STOP
671
678 if (mpProblemDefinition->GetDeformationAffectsCellModels() || mpProblemDefinition->GetDeformationAffectsConductivity())
679 {
680 // Determine the stretch and deformation gradient on each element.
681 //
682 // If mpProblemDefinition->GetDeformationAffectsCellModels()==true:
683 // Stretch will be passed to the cell models.
684 //
685 // If mpProblemDefinition->GetDeformationAffectsConductivity()==true:
686 // The deformation gradient needs to be set up but does not need to be passed to the tissue
687 // so that F is used to compute the conductivity. Instead this is
688 // done through the line "mpElectricsProblem->GetMonodomainTissue()->SetConductivityModifier(this);" line above, which means
689 // rCalculateModifiedConductivityTensor() will be called on this class by the tissue, which then uses the F
690
691 mpCardiacMechSolver->ComputeDeformationGradientAndStretchInEachElement(mDeformationGradientsForEachMechanicsElement, mStretchesForEachMechanicsElement);
692 }
693
694 if (mpProblemDefinition->GetDeformationAffectsCellModels())
695 {
696 // Set the stretches on each of the cell models
697 for (unsigned global_index = mpElectricsMesh->GetDistributedVectorFactory()->GetLow();
698 global_index < mpElectricsMesh->GetDistributedVectorFactory()->GetHigh();
699 global_index++)
700 {
701 unsigned containing_elem = mpMeshPair->rGetCoarseElementsForFineNodes()[global_index];
702 double stretch = mStretchesForEachMechanicsElement[containing_elem];
703 mpElectricsProblem->GetTissue()->GetCardiacCell(global_index)->SetStretch(stretch);
704 }
705 }
706
707 p_electrics_solver->SetTimeStep(HeartConfig::Instance()->GetPdeTimeStep());
708
714 LOG(2, " Solving electrics");
715 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::NON_MECH);
716 for (unsigned i=0; i<mNumElecTimestepsPerMechTimestep; i++)
717 {
718 double current_time = stepper.GetTime() + i*HeartConfig::Instance()->GetPdeTimeStep();
719 double next_time = stepper.GetTime() + (i+1)*HeartConfig::Instance()->GetPdeTimeStep();
720
721 // solve the electrics
722 p_electrics_solver->SetTimes(current_time, next_time);
723 p_electrics_solver->SetInitialCondition( initial_voltage );
724
725 electrics_solution = p_electrics_solver->Solve();
726
727 PetscReal min_voltage, max_voltage;
728 VecMax(electrics_solution,CHASTE_PETSC_NULLPTR,&max_voltage); //the second param is where the index would be returned
729 VecMin(electrics_solution,CHASTE_PETSC_NULLPTR,&min_voltage);
730 if (i==0)
731 {
732 LOG(2, " minimum and maximum voltage is " << min_voltage <<", "<<max_voltage);
733 }
734 else if (i==1)
735 {
736 LOG(2, " ..");
737 }
738
739 PetscTools::Destroy(initial_voltage);
740 initial_voltage = electrics_solution;
741 }
742
743 if (mpProblemDefinition->GetDeformationAffectsConductivity())
744 {
745 p_electrics_solver->SetMatrixIsNotAssembled();
746 }
747
748
754
755 // compute Ca_I at each quad point (by interpolation, using the info on which
756 // electrics element the quad point is in. Then set Ca_I on the mechanics solver
757 LOG(2, " Interpolating Ca_I and voltage");
758
759 //Collect the distributed calcium data into one Vec to be later replicated
760 for (unsigned node_index = 0; node_index<mpElectricsMesh->GetNumNodes(); node_index++)
761 {
762 if (mpElectricsMesh->GetDistributedVectorFactory()->IsGlobalIndexLocal(node_index))
763 {
764 double calcium_value = mpElectricsProblem->GetTissue()->GetCardiacCell(node_index)->GetIntracellularCalciumConcentration();
765 VecSetValue(calcium_data, node_index ,calcium_value, INSERT_VALUES);
766 }
767 }
768 PetscTools::Barrier();//not sure this is needed
769
770 //Replicate electrics solution and calcium (replication is inside this constructor of ReplicatableVector)
771 ReplicatableVector electrics_solution_repl(electrics_solution);//size=(number of electrics nodes)*ELEC_PROB_DIM
772 ReplicatableVector calcium_repl(calcium_data);//size = number of electrics nodes
773
774 //interpolate values onto the quad points of the mechanics mesh
775 for (unsigned i=0; i<mpMeshPair->rGetElementsAndWeights().size(); i++)
776 {
777 double interpolated_CaI = 0;
778 double interpolated_voltage = 0;
779
780 Element<DIM,DIM>& element = *(mpElectricsMesh->GetElement(mpMeshPair->rGetElementsAndWeights()[i].ElementNum));
781
782 for (unsigned node_index = 0; node_index<element.GetNumNodes(); node_index++)
783 {
784 unsigned global_index = element.GetNodeGlobalIndex(node_index);
785 double CaI_at_node = calcium_repl[global_index];
786 interpolated_CaI += CaI_at_node*mpMeshPair->rGetElementsAndWeights()[i].Weights(node_index);
787 //the following line assumes interleaved solution for ELEC_PROB_DIM>1 (e.g, [Vm_0, phi_e_0, Vm1, phi_e_1...])
788 interpolated_voltage += electrics_solution_repl[global_index*ELEC_PROB_DIM]*mpMeshPair->rGetElementsAndWeights()[i].Weights(node_index);
789 }
790
791 assert(i<mInterpolatedCalciumConcs.size());
792 assert(i<mInterpolatedVoltages.size());
793 mInterpolatedCalciumConcs[i] = interpolated_CaI;
794 mInterpolatedVoltages[i] = interpolated_voltage;
795 }
796
797 LOG(2, " Setting Ca_I. max value = " << Max(mInterpolatedCalciumConcs));
798
799 // NOTE IF NHS: HERE WE SHOULD PERHAPS CHECK WHETHER THE CELL MODELS HAVE Ca_Trop
800 // AND UPDATE FROM NHS TO CELL_MODEL, BUT NOT SURE HOW TO DO THIS.. (esp for implicit)
801
802 // set [Ca], V, t
803 mpCardiacMechSolver->SetCalciumAndVoltage(mInterpolatedCalciumConcs, mInterpolatedVoltages);
804 MechanicsEventHandler::EndEvent(MechanicsEventHandler::NON_MECH);
805
806
812 LOG(2, " Solving mechanics ");
813 mpMechanicsSolver->SetWriteOutput(false);
814
815 // make sure the mechanics solver knows the current time (in case
816 // the traction say is time-dependent).
817 mpMechanicsSolver->SetCurrentTime(stepper.GetTime());
818
819 // see if we will need to output stresses at the end of this timestep
820 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET
821 && (counter+1)%mNumTimestepsToOutputDeformationGradientsAndStress == 0)
822 {
823 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(true);
824 }
825
828// for (unsigned i=0; i<mpMechanicsSolver->rGetCurrentSolution().size(); i++)
829// {
830// double current = mpMechanicsSolver->rGetCurrentSolution()[i];
831// double previous = current_solution_previous_time_step[i];
832// double second_last = current_solution_second_last_time_step[i];
833// //double guess = 2*current - previous;
834// double guess = 3*current - 3*previous + second_last;
835//
836// if (!first_step)
837// {
838// current_solution_second_last_time_step[i] = current_solution_previous_time_step[i];
839// }
840// current_solution_previous_time_step[i] = mpMechanicsSolver->rGetCurrentSolution()[i];
841// mpMechanicsSolver->rGetCurrentSolution()[i] = guess;
842// }
843// first_step = false;
844
845 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::ALL_MECH);
846 mpCardiacMechSolver->Solve(stepper.GetTime(), stepper.GetNextTime(), mpProblemDefinition->GetContractionModelOdeTimestep());
847 MechanicsEventHandler::EndEvent(MechanicsEventHandler::ALL_MECH);
848
849 LOG(2, " Number of newton iterations = " << mpMechanicsSolver->GetNumNewtonIterations());
850
851 // update the current time
852 stepper.AdvanceOneTimeStep();
853 counter++;
854
855
861 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::OUTPUT);
862 if (mWriteOutput && (counter%WRITE_EVERY_NTH_TIME==0))
863 {
864 LOG(2, " Writing output");
865 // write deformed position
866 mech_writer_counter++;
867 mpMechanicsSolver->SetWriteOutput();
868 mpMechanicsSolver->WriteCurrentSpatialSolution("solution","nodes",mech_writer_counter);
869
870 p_cmgui_writer->WriteDeformationPositions(rGetDeformedPosition(), counter);
871
872#ifdef CHASTE_VTK
873 if (HeartConfig::Instance()->GetVisualizeWithVtk())
874 {
875 mpCardiacVtkWriter->WriteSolution(counter,electrics_solution_repl);//writer will pick up mech solution from solver
876 }
877#endif
878 if (!mNoElectricsOutput)
879 {
880 mpElectricsProblem->mpWriter->AdvanceAlongUnlimitedDimension();
881 mpElectricsProblem->WriteOneStep(stepper.GetTime(), electrics_solution);
882 }
883
884 if (mIsWatchedLocation)
885 {
886 WriteWatchedLocationData(stepper.GetTime(), electrics_solution);
887 }
888 OnEndOfTimeStep(counter);
889
890 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET && counter%mNumTimestepsToOutputDeformationGradientsAndStress==0)
891 {
892 mpMechanicsSolver->WriteCurrentStrains(DEFORMATION_GRADIENT_F,"deformation_gradient",mech_writer_counter);
893 mpMechanicsSolver->WriteCurrentAverageElementStresses("second_PK",mech_writer_counter);
894 }
895 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(false);
896 }
897 MechanicsEventHandler::EndEvent(MechanicsEventHandler::OUTPUT);
898
899 // write the total elapsed time..
901 } // end of main solve time loop
902
903 if ((mWriteOutput) && (!mNoElectricsOutput))
904 {
906 mpElectricsProblem->mpWriter->Close();
907 delete mpElectricsProblem->mpWriter;
908
909 std::string input_dir = mOutputDirectory+"/electrics";
910
911 // Convert simulation data to meshalyzer format - commented
912 std::string config_directory = HeartConfig::Instance()->GetOutputDirectory();
914
915// Hdf5ToMeshalyzerConverter<DIM,DIM> meshalyzer_converter(FileFinder(input_dir, RelativeTo::ChasteTestOutput),
916// "voltage", mpElectricsMesh,
917// HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering(),
918// HeartConfig::Instance()->GetVisualizerOutputPrecision() );
919 // Write mesh in a suitable form for meshalyzer
920 //std::string output_directory = mOutputDirectory + "/electrics/output";
921 // Write the mesh
922 //MeshalyzerMeshWriter<DIM,DIM> mesh_writer(output_directory, "mesh", false);
923 //mesh_writer.WriteFilesUsingMesh(*mpElectricsMesh);
924 // Write the parameters out
925 //HeartConfig::Instance()->Write();
926
927 // convert output to CMGUI format
929 "voltage", mpElectricsMesh, mHasBath,
930 HeartConfig::Instance()->GetVisualizerOutputPrecision());
931
932 // interpolate the electrical data onto the mechanics mesh nodes and write CMGUI...
933 // Note: this calculates the data on ALL nodes of the mechanics mesh (incl internal,
934 // non-vertex ones), which won't be used if linear CMGUI visualisation
935 // of the mechanics solution is used.
936 VoltageInterpolaterOntoMechanicsMesh<DIM> converter(*mpElectricsMesh,*mpMechanicsMesh);
937 converter.OutputToCmgui(variable_names,input_dir,"voltage");
938
939 // reset to the default value
940 HeartConfig::Instance()->SetOutputDirectory(config_directory);
941 }
942
943 if (p_cmgui_writer)
944 {
945 if (mNoElectricsOutput)
946 {
947 p_cmgui_writer->WriteCmguiScript("","undeformed");
948 }
949 else
950 {
951 p_cmgui_writer->WriteCmguiScript("../../electrics/cmgui_output/voltage_mechanics_mesh","undeformed");
952 }
953 delete p_cmgui_writer;
954 }
955 PetscTools::Destroy(electrics_solution);
956 PetscTools::Destroy(calcium_data);
957 delete p_electrics_solver;
958
959 MechanicsEventHandler::EndEvent(MechanicsEventHandler::ALL);
960}
961
962template<unsigned DIM, unsigned ELEC_PROB_DIM>
964{
965 double max = -1e200;
966 for (unsigned i=0; i<vec.size(); i++)
967 {
968 if (vec[i]>max) max=vec[i];
969 }
970 return max;
971}
972
973template<unsigned DIM, unsigned ELEC_PROB_DIM>
978
979template<unsigned DIM, unsigned ELEC_PROB_DIM>
981{
982 mIsWatchedLocation = true;
983 mWatchedLocation = watchedLocation;
984}
985
986template<unsigned DIM, unsigned ELEC_PROB_DIM>
988{
989 mNumTimestepsToOutputDeformationGradientsAndStress = (unsigned) floor((timeStep/mpProblemDefinition->GetMechanicsSolveTimestep())+0.5);
990 if (fabs(mNumTimestepsToOutputDeformationGradientsAndStress*mpProblemDefinition->GetMechanicsSolveTimestep() - timeStep) > 1e-6)
991 {
992 EXCEPTION("Timestep provided for SetOutputDeformationGradientsAndStress() is not a multiple of mechanics solve timestep");
993 }
994}
995
996template<unsigned DIM, unsigned ELEC_PROB_DIM>
998{
999 return mpMechanicsSolver->rGetDeformedPosition();
1000}
1001
1002// Explicit instantiation
1003
1004//note: 1d incompressible material doesn't make sense
#define EXCEPTION(message)
#define NEVER_REACHED
const unsigned UNSIGNED_UNSET
Definition Exception.hpp:53
#define CHASTE_PETSC_NULLPTR
A macro to define PETSc null pointer based on the PETSc version.
boost::numeric::ublas::c_matrix< T, 1, 1 > Inverse(const boost::numeric::ublas::c_matrix< T, 1, 1 > &rM)
void SetTimes(double tStart, double tEnd)
unsigned GetNumNodes() const
unsigned GetNodeGlobalIndex(unsigned localIndex) const
virtual void WriteFilesUsingMesh(AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, bool keepOriginalElementIndexing=true)
double Max(std::vector< double > &vec)
CardiacElectroMechanicsProblem(CompressibilityType compressibilityType, ElectricsProblemType electricsProblemType, TetrahedralMesh< DIM, DIM > *pElectricsMesh, QuadraticMesh< DIM > *pMechanicsMesh, AbstractCardiacCellFactory< DIM > *pCellFactory, ElectroMechanicsProblemDefinition< DIM > *pProblemDefinition, std::string outputDirectory)
c_matrix< double, DIM, DIM > & rCalculateModifiedConductivityTensor(unsigned elementIndex, const c_matrix< double, DIM, DIM > &rOriginalConductivity, unsigned domainIndex)
void SetWatchedPosition(c_vector< double, DIM > watchedLocation)
std::vector< c_vector< double, DIM > > & rGetDeformedPosition()
void WriteWatchedLocationData(double time, Vec voltage)
AbstractCardiacProblem< DIM, DIM, ELEC_PROB_DIM > * mpElectricsProblem
void WriteCmguiScript(std::string fieldBaseName="", std::string undeformedBaseName="")
void WriteDeformationPositions(std::vector< c_vector< double, DIM > > &rDeformedPositions, unsigned counter)
void WriteInitialMesh(std::string fileName="")
void SetAdditionalFieldNames(std::vector< std::string > &rFieldNames)
void SetRegionNames(std::vector< std::string > &rRegionNames)
bool OptionExists(const std::string &rOption)
static CommandLineArguments * Instance()
static AbstractCardiacProblem< DIM, DIM, 1u > * Create(ElectricsProblemType problemType, AbstractCardiacCellFactory< DIM > *pCellFactory)
static AbstractCardiacProblem< DIM, DIM, 2u > * Create(ElectricsProblemType problemType, AbstractCardiacCellFactory< DIM > *pCellFactory)
static AbstractCardiacProblem< DIM, DIM, PROBLEM_DIM > * Create(ElectricsProblemType problemType, AbstractCardiacCellFactory< DIM > *pCellFactory)
double GetPdeTimeStep() const
void SetPrintingTimeStep(double printingTimeStep)
void SetOutputDirectory(const std::string &rOutputDirectory)
void SetOutputFilenamePrefix(const std::string &rOutputFilenamePrefix)
static void Reset()
std::string GetOutputDirectory() const
static HeartConfig * Instance()
void Set(unsigned level, const std::string &rDirectory, const std::string &rFileName="log.txt")
Definition LogFile.cpp:73
static LogFile * Instance()
Definition LogFile.cpp:52
void WriteElapsedTime(std::string pre="")
Definition LogFile.cpp:116
void WriteHeader(std::string simulationType="")
Definition LogFile.cpp:111
static void Close()
Definition LogFile.cpp:101
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
static void Destroy(Vec &rVec)
static void Barrier(const std::string callerId="")
bool IsTimeAtEnd() const
double GetTime() const
void AdvanceOneTimeStep()
double GetNextTime() const
void OutputToCmgui(std::vector< std::string > &rVariableNames, std::string directory, std::string inputFileNamePrefix)