VoltageInterpolaterOntoMechanicsMesh<DIM>::VoltageInterpolaterOntoMechanicsMesh( TetrahedralMesh<DIM,DIM>& rElectricsMesh, QuadraticMesh<DIM>& rMechanicsMesh, std::vector<std::string>& rVariableNames, std::string directory, std::string inputFileNamePrefix) { // Read the data from the HDF5 file Hdf5DataReader reader(directory,inputFileNamePrefix); unsigned num_timesteps = reader.GetUnlimitedDimensionValues().size(); // set up the elements and weights for the coarse nodes in the fine mesh FineCoarseMeshPair<DIM> mesh_pair(rElectricsMesh, rMechanicsMesh); mesh_pair.SetUpBoxesOnFineMesh(); mesh_pair.ComputeFineElementsAndWeightsForCoarseNodes(true); assert(mesh_pair.rGetElementsAndWeights().size()==rMechanicsMesh.GetNumNodes()); // create and setup a writer Hdf5DataWriter* p_writer = new Hdf5DataWriter(*rMechanicsMesh.GetDistributedVectorFactory(), directory, "voltage_mechanics_mesh", false, //don't clean false); std::vector<int> columns_id; for (unsigned var_index = 0; var_index < rVariableNames.size(); var_index++) { std::string var_name = rVariableNames[var_index]; columns_id.push_back( p_writer->DefineVariable(var_name,"mV") ); } p_writer->DefineUnlimitedDimension("Time","msecs", num_timesteps); p_writer->DefineFixedDimension( rMechanicsMesh.GetNumNodes() ); p_writer->EndDefineMode(); assert(columns_id.size() == rVariableNames.size()); // set up a vector to read into DistributedVectorFactory factory(rElectricsMesh.GetNumNodes()); Vec voltage = factory.CreateVec(); std::vector<double> interpolated_voltages(rMechanicsMesh.GetNumNodes()); Vec voltage_coarse = NULL; for(unsigned time_step=0; time_step<num_timesteps; time_step++) { for (unsigned var_index = 0; var_index < rVariableNames.size(); var_index++) { std::string var_name = rVariableNames[var_index]; // read reader.GetVariableOverNodes(voltage, var_name, time_step); ReplicatableVector voltage_repl(voltage); // interpolate for(unsigned i=0; i<mesh_pair.rGetElementsAndWeights().size(); i++) { double interpolated_voltage = 0; Element<DIM,DIM>& element = *(rElectricsMesh.GetElement(mesh_pair.rGetElementsAndWeights()[i].ElementNum)); for(unsigned node_index = 0; node_index<element.GetNumNodes(); node_index++) { unsigned global_node_index = element.GetNodeGlobalIndex(node_index); interpolated_voltage += voltage_repl[global_node_index]*mesh_pair.rGetElementsAndWeights()[i].Weights(node_index); } interpolated_voltages[i] = interpolated_voltage; } if(voltage_coarse!=NULL) { PetscTools::Destroy(voltage_coarse); } voltage_coarse = PetscTools::CreateVec(interpolated_voltages); // write p_writer->PutVector(columns_id[var_index], voltage_coarse); } p_writer->PutUnlimitedVariable(time_step); p_writer->AdvanceAlongUnlimitedDimension(); } if(voltage_coarse!=NULL) { PetscTools::Destroy(voltage); PetscTools::Destroy(voltage_coarse); } // delete to flush delete p_writer; // Convert the new data to CMGUI format. // alter the directory in HeartConfig as that is where Hdf5ToCmguiConverter decides // where to output std::string config_directory = HeartConfig::Instance()->GetOutputDirectory(); HeartConfig::Instance()->SetOutputDirectory(directory); Hdf5ToCmguiConverter<DIM,DIM> converter(FileFinder(directory, RelativeTo::ChasteTestOutput), "voltage_mechanics_mesh", &rMechanicsMesh, false); HeartConfig::Instance()->SetOutputDirectory(config_directory); }
/* == Mechano-electric feedback, and alternative boundary conditions == * * Let us now run a simulation with mechano-electric feedback (MEF), and with different boundary conditions. */ void TestWithMef() throw(Exception) { /* If we want to use MEF, where the stretch (in the fibre-direction) couples back to the cell * model and is used in stretch-activated channels (SACs), we can't just let Chaste convert * from cellml to C++ code as usual (see electro-physiology tutorials on how cell model files * are autogenerated from CellML during compilation), since these files don't use stretch and don't * have SACs. We have to use pycml to create a cell model class for us, rename and save it, and * manually add the SAC current. * * There is one example of this already in the code-base, which we will use it the following * simulation. It is the Noble 98 model, with a SAC added that depends linearly on stretches (>1). * It is found in the file !NobleVargheseKohlNoble1998WithSac.hpp, and is called * `CML_noble_varghese_kohl_noble_1998_basic_with_sac`. * * To add a SAC current to (or otherwise alter) your favourite cell model, you have to do the following. * Auto-generate the C++ code, by running the following on the cellml file: * `./python/ConvertCellModel.py heart/src/odes/cellml/LuoRudy1991.cellml` * (see [wiki:ChasteGuides/CodeGenerationFromCellML#Usingthehelperscript ChasteGuides/CodeGenerationFromCellML#Usingthehelperscript] * if you want further documentation on this script). * * Copy and rename the resultant .hpp and .cpp files (which can be found in the same folder as the * input cellml). For example, rename everything to `LuoRudy1991WithSac`. Then alter the class * to overload the method `AbstractCardiacCell::SetStretch(double stretch)` to store the stretch, * and then implement the SAC in the `GetIIonic()` method. `CML_noble_varghese_kohl_noble_1998_basic_with_sac` * provides an example of the changes that need to be made. * * Let us create a cell factory returning these Noble98 SAC cells, but with no stimulus - the * SAC switching on will lead be to activation. */ ZeroStimulusCellFactory<CML_noble_varghese_kohl_noble_1998_basic_with_sac, 2> cell_factory; /* Construct two meshes are before, in 2D */ TetrahedralMesh<2,2> electrics_mesh; electrics_mesh.ConstructRegularSlabMesh(0.01/*stepsize*/, 0.1/*length*/, 0.1/*width*/, 0.1/*depth*/); QuadraticMesh<2> mechanics_mesh; mechanics_mesh.ConstructRegularSlabMesh(0.02, 0.1, 0.1, 0.1 /*as above with a different stepsize*/); /* Collect the fixed nodes. This time we directly specify the new locations. We say the * nodes on X=0 are to be fixed, setting the deformed x=0, but leaving y to be free * (sliding boundary conditions). This functionality is described in more detail in the * solid mechanics tutorials. */ std::vector<unsigned> fixed_nodes; std::vector<c_vector<double,2> > fixed_node_locations; fixed_nodes.push_back(0); fixed_node_locations.push_back(zero_vector<double>(2)); for(unsigned i=1; i<mechanics_mesh.GetNumNodes(); i++) { double X = mechanics_mesh.GetNode(i)->rGetLocation()[0]; if(fabs(X) < 1e-6) // ie, if X==0 { c_vector<double,2> new_position; new_position(0) = 0.0; new_position(1) = ElectroMechanicsProblemDefinition<2>::FREE; fixed_nodes.push_back(i); fixed_node_locations.push_back(new_position); } } /* Now specify tractions on the top and bottom surfaces. For full descriptions of how * to apply tractions see the solid mechanics tutorials. Here, we collect the boundary * elements on the bottom and top surfaces, and apply inward tractions - this will have the * effect of stretching the tissue in the X-direction. */ std::vector<BoundaryElement<1,2>*> boundary_elems; std::vector<c_vector<double,2> > tractions; c_vector<double,2> traction; for (TetrahedralMesh<2,2>::BoundaryElementIterator iter = mechanics_mesh.GetBoundaryElementIteratorBegin(); iter != mechanics_mesh.GetBoundaryElementIteratorEnd(); ++iter) { if (fabs((*iter)->CalculateCentroid()[1] - 0.0) < 1e-6) // if Y=0 { BoundaryElement<1,2>* p_element = *iter; boundary_elems.push_back(p_element); traction(0) = 0.0; // kPa, since the contraction model and material law use kPa for stiffnesses traction(1) = 1.0; // kPa, since the contraction model and material law use kPa for stiffnesses tractions.push_back(traction); } if (fabs((*iter)->CalculateCentroid()[1] - 0.1) < 1e-6) // if Y=0.1 { BoundaryElement<1,2>* p_element = *iter; boundary_elems.push_back(p_element); traction(0) = 0.0; traction(1) = -1.0; tractions.push_back(traction); } } /* Now set up the problem. We will use a compressible approach. */ ElectroMechanicsProblemDefinition<2> problem_defn(mechanics_mesh); problem_defn.SetContractionModel(KERCHOFFS2003,0.01/*contraction model ODE timestep*/); problem_defn.SetUseDefaultCardiacMaterialLaw(INCOMPRESSIBLE); problem_defn.SetMechanicsSolveTimestep(1.0); /* Set the fixed node and traction info. */ problem_defn.SetFixedNodes(fixed_nodes, fixed_node_locations); problem_defn.SetTractionBoundaryConditions(boundary_elems, tractions); /* Now say that the deformation should affect the electro-physiology */ problem_defn.SetDeformationAffectsElectrophysiology(false /*deformation affects conductivity*/, true /*deformation affects cell models*/); /* Set the end time, create the problem, and solve */ HeartConfig::Instance()->SetSimulationDuration(50.0); CardiacElectroMechanicsProblem<2,1> problem(INCOMPRESSIBLE, MONODOMAIN, &electrics_mesh, &mechanics_mesh, &cell_factory, &problem_defn, "TestCardiacElectroMechanicsWithMef"); problem.Solve(); /* Nothing exciting happens in the simulation as it is currently written. To get some interesting occurring, * alter the SAC conductance in the cell model from 0.035 to 0.35 (micro-Siemens). * (look for the line `const double g_sac = 0.035` in `NobleVargheseKohlNoble1998WithSac.hpp`). * * Rerun and visualise as usual, using Cmgui. By visualising the voltage on the deforming mesh, you can see that the * voltage gradually increases due to the SAC, since the tissue is stretched, until the threshold is reached * and activation occurs. * * For MEF simulations, we may want to visualise the electrical results on the electrics mesh using * Meshalyzer, for example to more easily visualise action potentials. This isn't (and currently * can't be) created by `CardiacElectroMechanicsProblem`. We can use a converter as follows * to post-process: */ FileFinder test_output_folder("TestCardiacElectroMechanicsWithMef/electrics", RelativeTo::ChasteTestOutput); Hdf5ToMeshalyzerConverter<2,2> converter(test_output_folder, "voltage", &electrics_mesh, false, HeartConfig::Instance()->GetVisualizerOutputPrecision()); /* Some other notes. If you want to apply time-dependent traction boundary conditions, this is possible by * specifying the traction in functional form - see solid mechanics tutorials. Similarly, more natural * 'pressure acting on the deformed body' boundary conditions are possible - see below tutorial. * * '''Robustness:''' Sometimes the nonlinear solver doesn't converge, and will give an error. This can be due to either * a non-physical (or not very physical) situation, or just because the current guess is quite far * from the solution and the solver can't find the solution (this can occur in nonlinear elasticity * problems if the loading is large, for example). Current work in progress is on making the solver * more robust, and also on parallelising the solver. One option when a solve fails is to decrease the * mechanics timestep. */ /* Ignore the following, it is just to check nothing has changed. */ Hdf5DataReader reader("TestCardiacElectroMechanicsWithMef/electrics", "voltage"); unsigned num_timesteps = reader.GetUnlimitedDimensionValues().size(); Vec voltage = PetscTools::CreateVec(electrics_mesh.GetNumNodes()); reader.GetVariableOverNodes(voltage, "V", num_timesteps-1); ReplicatableVector voltage_repl(voltage); for(unsigned i=0; i<voltage_repl.GetSize(); i++) { TS_ASSERT_DELTA(voltage_repl[i], -81.9080, 1e-3); } PetscTools::Destroy(voltage); }
// this method loads the output file from the previous method and computes the activation // time (defined as the time V becomes positive) for each node. void ConvertToActivationMap(double h, double dt, bool useSvi) { //TetrahedralMesh<3,3> mesh1; //double h1=0.01; // 0.01, 0.02, 0.05 //mesh1.ConstructRegularSlabMesh(h1, 2.0, 0.7, 0.3); //MeshalyzerMeshWriter<3,3> writer("Mesh0.01", "mesh01"); //writer.WriteFilesUsingMesh(mesh1); TetrahedralMesh<3,3> mesh; double printing_dt=0.1; mesh.ConstructRegularSlabMesh(h, 2.0, 0.7, 0.3); std::stringstream input_dir; input_dir << "Benchmark" << "_h" << h << "_dt" << dt; Hdf5DataReader reader(input_dir.str(),"results"); unsigned num_timesteps = reader.GetUnlimitedDimensionValues().size(); DistributedVectorFactory factory(mesh.GetNumNodes()); Vec voltage = factory.CreateVec(); std::vector<double> activation_times(mesh.GetNumNodes(), -1.0); std::vector<double> last_negative_voltage(mesh.GetNumNodes(), 1.0); for(unsigned timestep=0; timestep<num_timesteps; timestep++) { reader.GetVariableOverNodes(voltage, "V", timestep); ReplicatableVector voltage_repl(voltage); for(unsigned i=0; i<mesh.GetNumNodes(); i++) { double V = voltage_repl[i]; if(V > 0 && activation_times[i] < 0.0) { double old = last_negative_voltage[i]; assert(old < 0); activation_times[i] = (timestep-V/(V-old))*printing_dt; } else if (V<=0) { last_negative_voltage[i]=V; } } } OutputFileHandler handler("ActivationMaps", false); if (PetscTools::AmMaster() == false) { return; } //Only master proceeds to write c_vector<double, 3> top_corner; top_corner[0] = 2.0; top_corner[1] = 0.7; top_corner[2] = 0.3; c_vector<double, 3> unit_diagonal = top_corner/norm_2(top_corner); std::stringstream output_file1; output_file1 << "diagonal" << "_h" << h << "_dt" << dt; if (useSvi) { output_file1 << "_svi.dat"; } else { output_file1 << "_ici.dat"; } out_stream p_diag_file = handler.OpenOutputFile(output_file1.str()); for(unsigned i=0; i<mesh.GetNumNodes(); i++) { c_vector<double, 3> position = mesh.GetNode(i)->rGetLocation(); c_vector<double, 3> projected_diagonal = unit_diagonal*inner_prod(unit_diagonal, position); double off_diagonal = norm_2(position - projected_diagonal); if (off_diagonal < h/3) { double distance = norm_2(position); (*p_diag_file) << distance<<"\t"<< activation_times[i]<<"\t"<<off_diagonal<<"\n"; if( fabs(position[0]-2.0) < 1e-8) { std::cout << "h, dt = " << h << ", " << dt << "\n\t"; std::cout << "activation_times[" << i << "] = " << activation_times[i] << "\n"; } } } p_diag_file->close(); std::stringstream output_file; output_file << "activation" << "_h" << h << "_dt" << dt << ".dat"; out_stream p_file = handler.OpenOutputFile(output_file.str()); for(unsigned i=0; i<activation_times.size(); i++) { *p_file << activation_times[i] << "\n"; } p_file->close(); for(unsigned i=0; i<activation_times.size(); i++) { if(activation_times[i] < 0.0) { std::cout << "\n\n\n**Some nodes unactivated**\n\n\n"; output_file << "__error"; out_stream p_file2 = handler.OpenOutputFile(output_file.str()); p_file2->close(); return; } } }