Chaste Commit::29c3c4ab2bee00ddd311a035a1e522c03cbe928a
CardiacElectroMechanicsProblem.cpp
1/*
2
3Copyright (c) 2005-2026, 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.
511 AbstractDynamicLinearPdeSolver<DIM,DIM,ELEC_PROB_DIM>* p_electrics_solver = mpElectricsProblem->CreateSolver();
512
513 // set up initial voltage etc
514 Vec electrics_solution=NULL; //This will be set and used later
515 Vec calcium_data= mpElectricsMesh->GetDistributedVectorFactory()->CreateVec();
516 Vec initial_voltage = mpElectricsProblem->CreateInitialCondition();
517
518 // write the initial position
519 unsigned counter = 0;
520
521 TimeStepper stepper(0.0, HeartConfig::Instance()->GetSimulationDuration(), mpProblemDefinition->GetMechanicsSolveTimestep());
522
523 CmguiDeformedSolutionsWriter<DIM>* p_cmgui_writer = NULL;
524
525 std::vector<std::string> variable_names;
526
527 if (mWriteOutput)
528 {
529 if (HeartConfig::Instance()->GetVisualizeWithVtk())
530 {
531 ReplicatableVector ic(initial_voltage);
532 mpCardiacVtkWriter = new CardiacElectroMechanicsVtkHandler<DIM,ELEC_PROB_DIM>(*mpMechanicsSolver,
533 *mpMechanicsMesh,*mpElectricsMesh, ic, mDeformationOutputDirectory);
534 }
535 mpMechanicsSolver->SetWriteOutput();
536 mpMechanicsSolver->WriteCurrentSpatialSolution("undeformed","nodes");
537
538 p_cmgui_writer = new CmguiDeformedSolutionsWriter<DIM>(mOutputDirectory+"/deformation/cmgui",
539 "solution",
540 *(this->mpMechanicsMesh),
541 WRITE_QUADRATIC_MESH);
542
543 variable_names.push_back("V");
544 if (ELEC_PROB_DIM==2)
545 {
546 variable_names.push_back("Phi_e");
547 if (mHasBath==true)
548 {
549 std::vector<std::string> regions;
550 regions.push_back("tissue");
551 regions.push_back("bath");
552 p_cmgui_writer->SetRegionNames(regions);
553 }
554 }
555 p_cmgui_writer->SetAdditionalFieldNames(variable_names);
556 p_cmgui_writer->WriteInitialMesh("undeformed");
557 }
558
565
566 LOG(2, "\nSolving for initial deformation");
567 // LCOV_EXCL_START
568 if (verbose_during_solve)
569 {
570 std::cout << "\n\n ** Solving for initial deformation\n";
571 }
572 // LCOV_EXCL_STOP
573
574 mpMechanicsSolver->SetWriteOutput(false);
575
576 mpMechanicsSolver->SetCurrentTime(0.0);
577 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::ALL_MECH);
578
579 mpMechanicsSolver->SetIncludeActiveTension(false);
580 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET)
581 {
582 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(true);
583 }
584
585 unsigned total_newton_iters = 0;
586 for (unsigned index=1; index<=mpProblemDefinition->GetNumIncrementsForInitialDeformation(); index++)
587 {
588 // LCOV_EXCL_START
589 if (verbose_during_solve)
590 {
591 std::cout << " Increment " << index << " of " << mpProblemDefinition->GetNumIncrementsForInitialDeformation() << "\n";
592 }
593 // LCOV_EXCL_STOP
594
595 if (mpProblemDefinition->GetTractionBoundaryConditionType()==PRESSURE_ON_DEFORMED)
596 {
597 mpProblemDefinition->SetPressureScaling(((double)index)/mpProblemDefinition->GetNumIncrementsForInitialDeformation());
598 }
599 mpMechanicsSolver->Solve();
600
601 total_newton_iters += mpMechanicsSolver->GetNumNewtonIterations();
602 }
603
604 mpMechanicsSolver->SetIncludeActiveTension(true);
605 MechanicsEventHandler::EndEvent(MechanicsEventHandler::ALL_MECH);
606 LOG(2, " Number of newton iterations = " << total_newton_iters);
607
608
609 unsigned mech_writer_counter = 0;
610
611 if (mWriteOutput)
612 {
613 LOG(2, " Writing output");
614 mpMechanicsSolver->SetWriteOutput();
615 mpMechanicsSolver->WriteCurrentSpatialSolution("solution","nodes",mech_writer_counter);
616 p_cmgui_writer->WriteDeformationPositions(rGetDeformedPosition(), mech_writer_counter);
617
618 if (!mNoElectricsOutput)
619 {
620 // the writer inside monodomain problem uses the printing timestep
621 // inside HeartConfig to estimate total number of timesteps, so make
622 // sure this is set to what we will use.
623 HeartConfig::Instance()->SetPrintingTimeStep(mpProblemDefinition->GetMechanicsSolveTimestep());
624
625 // When we consider archiving these simulations we will need to get a bool back from the following
626 // command to decide whether or not the file is being extended, and hence whether to write the
627 // initial conditions into the .h5 file.
628 mpElectricsProblem->InitialiseWriter();
629 mpElectricsProblem->WriteOneStep(stepper.GetTime(), initial_voltage);
630 }
631
632 if (mIsWatchedLocation)
633 {
634 WriteWatchedLocationData(stepper.GetTime(), initial_voltage);
635 }
636
637 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET)
638 {
639 mpMechanicsSolver->WriteCurrentStrains(DEFORMATION_GRADIENT_F,"deformation_gradient",mech_writer_counter);
640 mpMechanicsSolver->WriteCurrentAverageElementStresses("second_PK",mech_writer_counter);
641 }
642 }
643
644
645 PrepareForSolve();
646
650// std::vector<double> current_solution_previous_time_step = mpMechanicsSolver->rGetCurrentSolution();
651// std::vector<double> current_solution_second_last_time_step = mpMechanicsSolver->rGetCurrentSolution();
652// bool first_step = true;
653
654
655 // reset this to false, may be reset again below
656 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(false);
657
658 while (!stepper.IsTimeAtEnd())
659 {
660 LOG(2, "\nCurrent time = " << stepper.GetTime());
661 // LCOV_EXCL_START
662 if (verbose_during_solve)
663 {
664 // also output time to screen as newton solve information will be output
665 std::cout << "\n\n ** Current time = " << stepper.GetTime() << "\n";
666 }
667 // LCOV_EXCL_STOP
668
675 if (mpProblemDefinition->GetDeformationAffectsCellModels() || mpProblemDefinition->GetDeformationAffectsConductivity())
676 {
677 // Determine the stretch and deformation gradient on each element.
678 //
679 // If mpProblemDefinition->GetDeformationAffectsCellModels()==true:
680 // Stretch will be passed to the cell models.
681 //
682 // If mpProblemDefinition->GetDeformationAffectsConductivity()==true:
683 // The deformation gradient needs to be set up but does not need to be passed to the tissue
684 // so that F is used to compute the conductivity. Instead this is
685 // done through the line "mpElectricsProblem->GetMonodomainTissue()->SetConductivityModifier(this);" line above, which means
686 // rCalculateModifiedConductivityTensor() will be called on this class by the tissue, which then uses the F
687
688 mpCardiacMechSolver->ComputeDeformationGradientAndStretchInEachElement(mDeformationGradientsForEachMechanicsElement, mStretchesForEachMechanicsElement);
689 }
690
691 if (mpProblemDefinition->GetDeformationAffectsCellModels())
692 {
693 // Set the stretches on each of the cell models
694 for (unsigned global_index = mpElectricsMesh->GetDistributedVectorFactory()->GetLow();
695 global_index < mpElectricsMesh->GetDistributedVectorFactory()->GetHigh();
696 global_index++)
697 {
698 unsigned containing_elem = mpMeshPair->rGetCoarseElementsForFineNodes()[global_index];
699 double stretch = mStretchesForEachMechanicsElement[containing_elem];
700 mpElectricsProblem->GetTissue()->GetCardiacCell(global_index)->SetStretch(stretch);
701 }
702 }
703
704 p_electrics_solver->SetTimeStep(HeartConfig::Instance()->GetPdeTimeStep());
705
711 LOG(2, " Solving electrics");
712 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::NON_MECH);
713 for (unsigned i=0; i<mNumElecTimestepsPerMechTimestep; i++)
714 {
715 double current_time = stepper.GetTime() + i*HeartConfig::Instance()->GetPdeTimeStep();
716 double next_time = stepper.GetTime() + (i+1)*HeartConfig::Instance()->GetPdeTimeStep();
717
718 // solve the electrics
719 p_electrics_solver->SetTimes(current_time, next_time);
720 p_electrics_solver->SetInitialCondition( initial_voltage );
721
722 electrics_solution = p_electrics_solver->Solve();
723
724 PetscReal min_voltage, max_voltage;
725 VecMax(electrics_solution,CHASTE_PETSC_NULLPTR,&max_voltage); //the second param is where the index would be returned
726 VecMin(electrics_solution,CHASTE_PETSC_NULLPTR,&min_voltage);
727 if (i==0)
728 {
729 LOG(2, " minimum and maximum voltage is " << min_voltage <<", "<<max_voltage);
730 }
731 else if (i==1)
732 {
733 LOG(2, " ..");
734 }
735
736 PetscTools::Destroy(initial_voltage);
737 initial_voltage = electrics_solution;
738 }
739
740 if (mpProblemDefinition->GetDeformationAffectsConductivity())
741 {
742 p_electrics_solver->SetMatrixIsNotAssembled();
743 }
744
745
751
752 // compute Ca_I at each quad point (by interpolation, using the info on which
753 // electrics element the quad point is in. Then set Ca_I on the mechanics solver
754 LOG(2, " Interpolating Ca_I and voltage");
755
756 //Collect the distributed calcium data into one Vec to be later replicated
757 for (unsigned node_index = 0; node_index<mpElectricsMesh->GetNumNodes(); node_index++)
758 {
759 if (mpElectricsMesh->GetDistributedVectorFactory()->IsGlobalIndexLocal(node_index))
760 {
761 double calcium_value = mpElectricsProblem->GetTissue()->GetCardiacCell(node_index)->GetIntracellularCalciumConcentration();
762 VecSetValue(calcium_data, node_index ,calcium_value, INSERT_VALUES);
763 }
764 }
765 PetscTools::Barrier();//not sure this is needed
766
767 //Replicate electrics solution and calcium (replication is inside this constructor of ReplicatableVector)
768 ReplicatableVector electrics_solution_repl(electrics_solution);//size=(number of electrics nodes)*ELEC_PROB_DIM
769 ReplicatableVector calcium_repl(calcium_data);//size = number of electrics nodes
770
771 //interpolate values onto the quad points of the mechanics mesh
772 for (unsigned i=0; i<mpMeshPair->rGetElementsAndWeights().size(); i++)
773 {
774 double interpolated_CaI = 0;
775 double interpolated_voltage = 0;
776
777 Element<DIM,DIM>& element = *(mpElectricsMesh->GetElement(mpMeshPair->rGetElementsAndWeights()[i].ElementNum));
778
779 for (unsigned node_index = 0; node_index<element.GetNumNodes(); node_index++)
780 {
781 unsigned global_index = element.GetNodeGlobalIndex(node_index);
782 double CaI_at_node = calcium_repl[global_index];
783 interpolated_CaI += CaI_at_node*mpMeshPair->rGetElementsAndWeights()[i].Weights(node_index);
784 //the following line assumes interleaved solution for ELEC_PROB_DIM>1 (e.g, [Vm_0, phi_e_0, Vm1, phi_e_1...])
785 interpolated_voltage += electrics_solution_repl[global_index*ELEC_PROB_DIM]*mpMeshPair->rGetElementsAndWeights()[i].Weights(node_index);
786 }
787
788 assert(i<mInterpolatedCalciumConcs.size());
789 assert(i<mInterpolatedVoltages.size());
790 mInterpolatedCalciumConcs[i] = interpolated_CaI;
791 mInterpolatedVoltages[i] = interpolated_voltage;
792 }
793
794 LOG(2, " Setting Ca_I. max value = " << Max(mInterpolatedCalciumConcs));
795
796 // NOTE IF NHS: HERE WE SHOULD PERHAPS CHECK WHETHER THE CELL MODELS HAVE Ca_Trop
797 // AND UPDATE FROM NHS TO CELL_MODEL, BUT NOT SURE HOW TO DO THIS.. (esp for implicit)
798
799 // set [Ca], V, t
800 mpCardiacMechSolver->SetCalciumAndVoltage(mInterpolatedCalciumConcs, mInterpolatedVoltages);
801 MechanicsEventHandler::EndEvent(MechanicsEventHandler::NON_MECH);
802
803
809 LOG(2, " Solving mechanics ");
810 mpMechanicsSolver->SetWriteOutput(false);
811
812 // make sure the mechanics solver knows the current time (in case
813 // the traction say is time-dependent).
814 mpMechanicsSolver->SetCurrentTime(stepper.GetTime());
815
816 // see if we will need to output stresses at the end of this timestep
817 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET
818 && (counter+1)%mNumTimestepsToOutputDeformationGradientsAndStress == 0)
819 {
820 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(true);
821 }
822
825// for (unsigned i=0; i<mpMechanicsSolver->rGetCurrentSolution().size(); i++)
826// {
827// double current = mpMechanicsSolver->rGetCurrentSolution()[i];
828// double previous = current_solution_previous_time_step[i];
829// double second_last = current_solution_second_last_time_step[i];
830// //double guess = 2*current - previous;
831// double guess = 3*current - 3*previous + second_last;
832//
833// if (!first_step)
834// {
835// current_solution_second_last_time_step[i] = current_solution_previous_time_step[i];
836// }
837// current_solution_previous_time_step[i] = mpMechanicsSolver->rGetCurrentSolution()[i];
838// mpMechanicsSolver->rGetCurrentSolution()[i] = guess;
839// }
840// first_step = false;
841
842 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::ALL_MECH);
843 mpCardiacMechSolver->Solve(stepper.GetTime(), stepper.GetNextTime(), mpProblemDefinition->GetContractionModelOdeTimestep());
844 MechanicsEventHandler::EndEvent(MechanicsEventHandler::ALL_MECH);
845
846 LOG(2, " Number of newton iterations = " << mpMechanicsSolver->GetNumNewtonIterations());
847
848 // update the current time
849 stepper.AdvanceOneTimeStep();
850 counter++;
851
852
858 MechanicsEventHandler::BeginEvent(MechanicsEventHandler::OUTPUT);
859 if (mWriteOutput && (counter%WRITE_EVERY_NTH_TIME==0))
860 {
861 LOG(2, " Writing output");
862 // write deformed position
863 mech_writer_counter++;
864 mpMechanicsSolver->SetWriteOutput();
865 mpMechanicsSolver->WriteCurrentSpatialSolution("solution","nodes",mech_writer_counter);
866
867 p_cmgui_writer->WriteDeformationPositions(rGetDeformedPosition(), counter);
868
869 if (HeartConfig::Instance()->GetVisualizeWithVtk())
870 {
871 mpCardiacVtkWriter->WriteSolution(counter,electrics_solution_repl);//writer will pick up mech solution from solver
872 }
873 if (!mNoElectricsOutput)
874 {
875 mpElectricsProblem->mpWriter->AdvanceAlongUnlimitedDimension();
876 mpElectricsProblem->WriteOneStep(stepper.GetTime(), electrics_solution);
877 }
878
879 if (mIsWatchedLocation)
880 {
881 WriteWatchedLocationData(stepper.GetTime(), electrics_solution);
882 }
883 OnEndOfTimeStep(counter);
884
885 if (mNumTimestepsToOutputDeformationGradientsAndStress!=UNSIGNED_UNSET && counter%mNumTimestepsToOutputDeformationGradientsAndStress==0)
886 {
887 mpMechanicsSolver->WriteCurrentStrains(DEFORMATION_GRADIENT_F,"deformation_gradient",mech_writer_counter);
888 mpMechanicsSolver->WriteCurrentAverageElementStresses("second_PK",mech_writer_counter);
889 }
890 mpMechanicsSolver->SetComputeAverageStressPerElementDuringSolve(false);
891 }
892 MechanicsEventHandler::EndEvent(MechanicsEventHandler::OUTPUT);
893
894 // write the total elapsed time..
896 } // end of main solve time loop
897
898 if ((mWriteOutput) && (!mNoElectricsOutput))
899 {
901 mpElectricsProblem->mpWriter->Close();
902 delete mpElectricsProblem->mpWriter;
903
904 std::string input_dir = mOutputDirectory+"/electrics";
905
906 // Convert simulation data to meshalyzer format - commented
907 std::string config_directory = HeartConfig::Instance()->GetOutputDirectory();
909
910// Hdf5ToMeshalyzerConverter<DIM,DIM> meshalyzer_converter(FileFinder(input_dir, RelativeTo::ChasteTestOutput),
911// "voltage", mpElectricsMesh,
912// HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering(),
913// HeartConfig::Instance()->GetVisualizerOutputPrecision() );
914 // Write mesh in a suitable form for meshalyzer
915 //std::string output_directory = mOutputDirectory + "/electrics/output";
916 // Write the mesh
917 //MeshalyzerMeshWriter<DIM,DIM> mesh_writer(output_directory, "mesh", false);
918 //mesh_writer.WriteFilesUsingMesh(*mpElectricsMesh);
919 // Write the parameters out
920 //HeartConfig::Instance()->Write();
921
922 // convert output to CMGUI format
924 "voltage", mpElectricsMesh, mHasBath,
925 HeartConfig::Instance()->GetVisualizerOutputPrecision());
926
927 // interpolate the electrical data onto the mechanics mesh nodes and write CMGUI...
928 // Note: this calculates the data on ALL nodes of the mechanics mesh (incl internal,
929 // non-vertex ones), which won't be used if linear CMGUI visualisation
930 // of the mechanics solution is used.
931 VoltageInterpolaterOntoMechanicsMesh<DIM> converter(*mpElectricsMesh,*mpMechanicsMesh);
932 converter.OutputToCmgui(variable_names,input_dir,"voltage");
933
934 // reset to the default value
935 HeartConfig::Instance()->SetOutputDirectory(config_directory);
936 }
937
938 if (p_cmgui_writer)
939 {
940 if (mNoElectricsOutput)
941 {
942 p_cmgui_writer->WriteCmguiScript("","undeformed");
943 }
944 else
945 {
946 p_cmgui_writer->WriteCmguiScript("../../electrics/cmgui_output/voltage_mechanics_mesh","undeformed");
947 }
948 delete p_cmgui_writer;
949 }
950 PetscTools::Destroy(electrics_solution);
951 PetscTools::Destroy(calcium_data);
952 delete p_electrics_solver;
953
954 MechanicsEventHandler::EndEvent(MechanicsEventHandler::ALL);
955}
956
957template<unsigned DIM, unsigned ELEC_PROB_DIM>
959{
960 double max = -1e200;
961 for (unsigned i=0; i<vec.size(); i++)
962 {
963 if (vec[i]>max) max=vec[i];
964 }
965 return max;
966}
967
968template<unsigned DIM, unsigned ELEC_PROB_DIM>
973
974template<unsigned DIM, unsigned ELEC_PROB_DIM>
976{
977 mIsWatchedLocation = true;
978 mWatchedLocation = watchedLocation;
979}
980
981template<unsigned DIM, unsigned ELEC_PROB_DIM>
983{
984 mNumTimestepsToOutputDeformationGradientsAndStress = (unsigned) floor((timeStep/mpProblemDefinition->GetMechanicsSolveTimestep())+0.5);
985 if (fabs(mNumTimestepsToOutputDeformationGradientsAndStress*mpProblemDefinition->GetMechanicsSolveTimestep() - timeStep) > 1e-6)
986 {
987 EXCEPTION("Timestep provided for SetOutputDeformationGradientsAndStress() is not a multiple of mechanics solve timestep");
988 }
989}
990
991template<unsigned DIM, unsigned ELEC_PROB_DIM>
993{
994 return mpMechanicsSolver->rGetDeformedPosition();
995}
996
997// Explicit instantiation
998
999//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)