Chaste Commit::6e4f5fe395bca70eb7641cf6e0e87f450383ca5a
PostProcessingWriter.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
37#include "HeartConfig.hpp"
38#include "PostProcessingWriter.hpp"
39#include "PetscTools.hpp"
40#include "OutputFileHandler.hpp"
41#include "DistanceMapCalculator.hpp"
42#include "PseudoEcgCalculator.hpp"
43#include "Version.hpp"
44#include "HeartEventHandler.hpp"
45#include "Hdf5DataWriter.hpp"
46
47#include <iostream>
48
49template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
51 const FileFinder& rDirectory,
52 const std::string& rHdf5FileName,
53 const std::string& rVoltageName,
54 hsize_t hdf5DataWriterChunkSize)
55 : mDirectory(rDirectory),
56 mHdf5File(rHdf5FileName),
57 mVoltageName(rVoltageName),
58 mrMesh(rMesh),
59 mHdf5DataWriterChunkSize(hdf5DataWriterChunkSize)
60{
61 mLo = mrMesh.GetDistributedVectorFactory()->GetLow();
62 mHi = mrMesh.GetDistributedVectorFactory()->GetHigh();
65 // Check that the hdf file was generated by simulations from (probably) the same mesh.
66 assert(mpDataReader->GetNumberOfRows() == mrMesh.GetNumNodes());
67}
68
69template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
71{
72 //Check that post-processing is really needed
73 assert(HeartConfig::Instance()->IsPostProcessingRequested());
74
75 // Please note that only the master processor should write to file.
76 // Each of the private methods called here takes care of checking.
77 if (HeartConfig::Instance()->IsApdMapsRequested())
78 {
79 std::vector<std::pair<double,double> > apd_maps;
81 for (unsigned i=0; i<apd_maps.size(); i++)
82 {
83 WriteApdMapFile(apd_maps[i].first, apd_maps[i].second);
84 }
85 }
86
87 if (HeartConfig::Instance()->IsUpstrokeTimeMapsRequested())
88 {
89 std::vector<double> upstroke_time_maps;
90 HeartConfig::Instance()->GetUpstrokeTimeMaps(upstroke_time_maps);
91 for (unsigned i=0; i<upstroke_time_maps.size(); i++)
92 {
93 WriteUpstrokeTimeMap(upstroke_time_maps[i]);
94 }
95 }
96
97 if (HeartConfig::Instance()->IsMaxUpstrokeVelocityMapRequested())
98 {
99 std::vector<double> upstroke_velocity_maps;
100 HeartConfig::Instance()->GetMaxUpstrokeVelocityMaps(upstroke_velocity_maps);
101 for (unsigned i=0; i<upstroke_velocity_maps.size(); i++)
102 {
103 WriteMaxUpstrokeVelocityMap(upstroke_velocity_maps[i]);
104 }
105 }
106
107 if (HeartConfig::Instance()->IsConductionVelocityMapsRequested())
108 {
109 std::vector<unsigned> conduction_velocity_maps;
110 HeartConfig::Instance()->GetConductionVelocityMaps(conduction_velocity_maps);
111
112 //get the mesh here
113 DistanceMapCalculator<ELEMENT_DIM, SPACE_DIM> dist_map_calculator(mrMesh);
114
115 for (unsigned i=0; i<conduction_velocity_maps.size(); i++)
116 {
117 std::vector<double> distance_map;
118 std::vector<unsigned> origin_surface;
119 origin_surface.push_back(conduction_velocity_maps[i]);
120 dist_map_calculator.ComputeDistanceMap(origin_surface, distance_map);
121 WriteConductionVelocityMap(conduction_velocity_maps[i], distance_map);
122 }
123 }
124
125 if (HeartConfig::Instance()->IsAnyNodalTimeTraceRequested())
126 {
127 std::vector<unsigned> requested_nodes;
129 WriteVariablesOverTimeAtNodes(requested_nodes);
130 }
131
132 if (HeartConfig::Instance()->IsPseudoEcgCalculationRequested())
133 {
134 std::vector<ChastePoint<SPACE_DIM> > electrodes;
136
139 delete mpDataReader;
140
141 for (unsigned i=0; i<electrodes.size(); i++)
142 {
144 electrodes[i],
145 mDirectory,
146 mHdf5File,
147 mVoltageName);
148 calculator.WritePseudoEcg();
149 }
150
152 mpDataReader = new Hdf5DataReader(mDirectory, mHdf5File);
153 mpCalculator->SetHdf5DataReader(mpDataReader);
154 }
155}
156
157template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
159{
160 delete mpDataReader;
161 delete mpCalculator;
162}
163
164template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
165void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteOutputDataToHdf5(const std::vector<std::vector<double> >& rDataPayload,
166 const std::string& rDatasetName,
167 const std::string& rDatasetUnit,
168 const std::string& rUnlimitedVariableName,
169 const std::string& rUnlimitedVariableUnit)
170{
171 DistributedVectorFactory* p_factory = mrMesh.GetDistributedVectorFactory();
173 Hdf5DataWriter writer(*p_factory,
174 mDirectory.GetRelativePath(test_output), // Path relative to CHASTE_TEST_OUTPUT
175 mHdf5File,
176 false, // to wiping
177 true, // to extending
178 rDatasetName); // dataset name
179
180 /* Probe define mode. We asked to extend, so if the writer is currently in define mode it means the
181 * dataset doesn't exist yet and needs creating. If it is NOT in define mode, it means the dataset
182 * exists, so we'll empty it and calculate new postprocessing data. */
183 int apd_id;
184 if (writer.IsInDefineMode())
185 {
186 apd_id = writer.DefineVariable(rDatasetName, rDatasetUnit);
187 writer.DefineFixedDimension(mrMesh.GetNumNodes());
188 writer.DefineUnlimitedDimension(rUnlimitedVariableName, rUnlimitedVariableUnit);
189 if (mHdf5DataWriterChunkSize>0u)
190 {
191 /* Pass target chunk size through to writer. (We don't do the
192 * alignment one as well because that can only be done for a new
193 * file and PostProcessingWriter can only add to an existing file.)
194 */
195 writer.SetTargetChunkSize(mHdf5DataWriterChunkSize);
196 }
197 writer.EndDefineMode();
198 }
199 else
200 {
201 apd_id = writer.GetVariableByName(rDatasetName);
202 writer.EmptyDataset();
203 }
204
205 //Determine the maximum number of paces
206 unsigned local_max_paces = 0u;
207 for (unsigned node_index = 0; node_index < rDataPayload.size(); ++node_index)
208 {
209 if (rDataPayload[node_index].size() > local_max_paces)
210 {
211 local_max_paces = rDataPayload[node_index].size();
212 }
213 }
214
215 unsigned max_paces = 0u;
216 MPI_Allreduce(&local_max_paces, &max_paces, 1, MPI_UNSIGNED, MPI_MAX, PETSC_COMM_WORLD);
217
218 for (unsigned pace_idx = 0; pace_idx < max_paces; pace_idx++)
219 {
220 Vec apd_vec = p_factory->CreateVec();
221 DistributedVector distributed_vector = p_factory->CreateDistributedVector(apd_vec);
222 for (DistributedVector::Iterator index = distributed_vector.Begin();
223 index!= distributed_vector.End();
224 ++index)
225 {
226 unsigned node_idx = index.Local;
227 // pad with -999 if no pace defined at this node
228 if (pace_idx < rDataPayload[node_idx].size() )
229 {
230 distributed_vector[index] = rDataPayload[node_idx][pace_idx];
231 }
232 else
233 {
234 distributed_vector[index] = -999.0;
235 }
236 }
237 writer.PutVector(apd_id, apd_vec);
238 PetscTools::Destroy(apd_vec);
239 writer.PutUnlimitedVariable(pace_idx);
241 }
242 writer.Close();
243}
244
245template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
246void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteApdMapFile(double repolarisationPercentage, double threshold)
247{
248 std::vector<std::vector<double> > local_output_data = mpCalculator->CalculateAllActionPotentialDurationsForNodeRange(repolarisationPercentage, mLo, mHi, threshold);
249
250 // HDF5 shouldn't have minus signs in the data names..
251 std::stringstream hdf5_dataset_name;
252 hdf5_dataset_name << "Apd_" << repolarisationPercentage;
253
254 WriteOutputDataToHdf5(local_output_data,
255 hdf5_dataset_name.str() + ConvertToHdf5FriendlyString(threshold) + "_Map",
256 "msec");
257}
258
259template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
261{
262 std::vector<std::vector<double> > output_data;
263 //Fill in data
264 for (unsigned node_index = mLo; node_index < mHi; node_index++)
265 {
266 std::vector<double> upstroke_times;
267 try
268 {
269 upstroke_times = mpCalculator->CalculateUpstrokeTimes(node_index, threshold);
270 assert(upstroke_times.size() != 0);
271 }
272 catch(Exception&)
273 {
274 upstroke_times.push_back(0);
275 assert(upstroke_times.size() == 1);
276 }
277 output_data.push_back(upstroke_times);
278 }
279
280 WriteOutputDataToHdf5(output_data,
281 "UpstrokeTimeMap" + ConvertToHdf5FriendlyString(threshold),
282 "msec");
283}
284
285template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
287{
288 std::vector<std::vector<double> > output_data;
289 //Fill in data
290 for (unsigned node_index = mLo; node_index < mHi; node_index++)
291 {
292 std::vector<double> upstroke_velocities;
293 try
294 {
295 upstroke_velocities = mpCalculator->CalculateAllMaximumUpstrokeVelocities(node_index, threshold);
296 assert(upstroke_velocities.size() != 0);
297 }
298 catch(Exception&)
299 {
300 upstroke_velocities.push_back(0);
301 assert(upstroke_velocities.size() ==1);
302 }
303 output_data.push_back(upstroke_velocities);
304 }
305
306 WriteOutputDataToHdf5(output_data,
307 "MaxUpstrokeVelocityMap" + ConvertToHdf5FriendlyString(threshold),
308 "mV_per_msec");
309}
310
311template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
312void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteConductionVelocityMap(unsigned originNode, std::vector<double> distancesFromOriginNode)
313{
314 //Fill in data
315 std::vector<std::vector<double> > output_data;
316 for (unsigned dest_node = mLo; dest_node < mHi; dest_node++)
317 {
318 std::vector<double> conduction_velocities;
319 try
320 {
321 conduction_velocities = mpCalculator->CalculateAllConductionVelocities(originNode, dest_node, distancesFromOriginNode[dest_node]);
322 assert(conduction_velocities.size() != 0);
323 }
324 catch(Exception&)
325 {
326 conduction_velocities.push_back(0);
327 assert(conduction_velocities.size() == 1);
328 }
329 output_data.push_back(conduction_velocities);
330 }
331 std::stringstream filename_stream;
332 filename_stream << "ConductionVelocityFromNode" << originNode;
333
334 WriteOutputDataToHdf5(output_data, filename_stream.str(), "cm_per_msec");
335}
336
337template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
339{
340 std::vector<std::vector<double> > output_data;
341
342 //Fill in data
343 for (unsigned node_index = mLo; node_index < mHi; node_index++)
344 {
345 std::vector<double> upstroke_velocities;
346 std::vector<unsigned> above_threshold_depolarisations;
347 std::vector<double> output_item;
348 bool no_upstroke_occurred = false;
349
350 try
351 {
352 upstroke_velocities = mpCalculator->CalculateAllMaximumUpstrokeVelocities(node_index, threshold);
353 assert(upstroke_velocities.size() != 0);
354 }
355 catch(Exception&)
356 {
357 upstroke_velocities.push_back(0);
358 assert(upstroke_velocities.size() == 1);
359 no_upstroke_occurred = true;
360 }
361 // This method won't throw any exception, so there is no need to put it into the try/catch:
362 above_threshold_depolarisations = mpCalculator->CalculateAllAboveThresholdDepolarisations(node_index, threshold);
363
364 // Count the total above threshold depolarisations
365 unsigned total_number_of_above_threshold_depolarisations = 0;
366 for (unsigned ead_index = 0; ead_index< above_threshold_depolarisations.size();ead_index++)
367 {
368 total_number_of_above_threshold_depolarisations = total_number_of_above_threshold_depolarisations + above_threshold_depolarisations[ead_index];
369 }
370
371 // For this item, push back the number of upstrokes...
372 if (no_upstroke_occurred)
373 {
374 output_item.push_back(0);
375 }
376 else
377 {
378 output_item.push_back((double)upstroke_velocities.size());
379 }
380 //... and the number of above threshold depolarisations
381 output_item.push_back((double) total_number_of_above_threshold_depolarisations);
382
383 output_data.push_back(output_item);
384 }
385
386 // we just use meshalyzer format so that something is generated in simple column format for use with gnuplot etc.
387 WriteGenericFileToMeshalyzer(output_data, "", "AboveThresholdDepolarisations" + ConvertToHdf5FriendlyString(threshold) +
388 ".dat");
389}
390
391template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
393{
394 std::stringstream dataset_stream;
395 std::string extra_message = "_";
396 if (threshold < 0.0)
397 {
398 extra_message += "minus_";
399 threshold = -threshold;
400 }
401
402 // if threshold is a decimal eg: because using phenomenological model
403 if (threshold - floor(threshold) > 1e-8)
404 {
405 // give the answer to 2dp
406 dataset_stream << extra_message << floor(threshold) << "pt" << floor(threshold*100)-(floor(threshold)*100);
407 }
408 else
409 {
410 dataset_stream << extra_message << threshold;
411 }
412
413 return dataset_stream.str();
414}
415
416template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
418{
419 std::vector<std::string> variable_names = mpDataReader->GetVariableNames();
420
421 //we will write one file per variable in the hdf5 file
422 for (unsigned name_index=0; name_index < variable_names.size(); name_index++)
423 {
424 std::vector<std::vector<double> > output_data;
425 if (PetscTools::AmMaster())//only master process fills the data structure
426 {
427 //allocate memory: NXM matrix where N = number of time steps and M number of requested nodes
428 output_data.resize( mpDataReader->GetUnlimitedDimensionValues().size() );
429 for (unsigned j = 0; j < mpDataReader->GetUnlimitedDimensionValues().size(); j++)
430 {
431 output_data[j].resize(rNodeIndices.size());
432 }
433
434 for (unsigned requested_index = 0; requested_index < rNodeIndices.size(); requested_index++)
435 {
436 unsigned node_index = rNodeIndices[requested_index];
437
438 // Handle permutation, if any
439 if ((mrMesh.rGetNodePermutation().size() != 0) &&
441 {
442 node_index = mrMesh.rGetNodePermutation()[ rNodeIndices[requested_index] ];
443 }
444
445 // Grab the data from the hdf5 file.
446 std::vector<double> time_series = mpDataReader->GetVariableOverTime(variable_names[name_index], node_index);
447 assert ( time_series.size() == mpDataReader->GetUnlimitedDimensionValues().size());
448
449 // Fill the output_data data structure
450 for (unsigned time_step = 0; time_step < time_series.size(); time_step++)
451 {
452 output_data[time_step][requested_index] = time_series[time_step];
453 }
454 }
455 }
456 std::stringstream filename_stream;
457 filename_stream << "NodalTraces_" << variable_names[name_index] << ".dat";
458 // we just use meshalyzer format so that something is generated in simple column format for use with gnuplot etc.
459 WriteGenericFileToMeshalyzer(output_data, "", filename_stream.str());
460 }
461}
462
463template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
464void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteGenericFileToMeshalyzer(std::vector<std::vector<double> >& rDataPayload, const std::string& rFolder, const std::string& rFileName)
465{
466 OutputFileHandler output_file_handler(HeartConfig::Instance()->GetOutputDirectory() + "/" + rFolder, false);
468 {
469 out_stream p_file=out_stream(NULL);
470 //Open file
472 {
473 // Open the file for the first time
474 p_file = output_file_handler.OpenOutputFile(rFileName);
475 }
476 else
477 {
478 // Append data to the existing file opened by master
479 p_file = output_file_handler.OpenOutputFile(rFileName, std::ios::app);
480 }
481 // Write data
482 for (unsigned line_number=0; line_number<rDataPayload.size(); line_number++)
483 {
484 for (unsigned i = 0; i < rDataPayload[line_number].size(); i++)
485 {
486 *p_file << rDataPayload[line_number][i] << "\t";
487 }
488 *p_file << std::endl;
489 }
490
491 // Last processor appends comment line
493 {
494 std::string comment = "# " + ChasteBuildInfo::GetProvenanceString();
495 *p_file << comment;
496 }
497 p_file->close();
498 }
499 //There's a barrier included here: Process i+1 waits for process i to close the file
501}
502
503// Explicit instantiation
504template class PostProcessingWriter<1,1>;
505template class PostProcessingWriter<1,2>;
506template class PostProcessingWriter<2,2>;
507template class PostProcessingWriter<1,3>;
508template class PostProcessingWriter<3,3>;
static std::string GetProvenanceString()
void ComputeDistanceMap(const std::vector< unsigned > &rSourceNodeIndices, std::vector< double > &rNodeDistances)
DistributedVector CreateDistributedVector(Vec vec, bool readOnly=false)
unsigned GetNumberOfRows()
void SetTargetChunkSize(hsize_t targetSize)
void DefineUnlimitedDimension(const std::string &rVariableName, const std::string &rVariableUnits, unsigned estimatedLength=1)
void PutUnlimitedVariable(double value)
void AdvanceAlongUnlimitedDimension()
int GetVariableByName(const std::string &rVariableName)
void DefineFixedDimension(long dimensionSize)
int DefineVariable(const std::string &rVariableName, const std::string &rVariableUnits)
virtual void EndDefineMode()
void PutVector(int variableID, Vec petscVector)
bool GetOutputUsingOriginalNodeOrdering()
void GetNodalTimeTraceRequested(std::vector< unsigned > &rRequestedNodes) const
void GetApdMaps(std::vector< std::pair< double, double > > &rApdMaps) const
void GetConductionVelocityMaps(std::vector< unsigned > &rConductionVelocityMaps) const
void GetPseudoEcgElectrodePositions(std::vector< ChastePoint< SPACE_DIM > > &rPseudoEcgElectrodePositions) const
void GetUpstrokeTimeMaps(std::vector< double > &rUpstrokeTimeMaps) const
void GetMaxUpstrokeVelocityMaps(std::vector< double > &rUpstrokeVelocityMaps) const
static HeartConfig * Instance()
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 bool AmMaster()
static bool AmTopMost()
static void EndRoundRobin()
static void BeginRoundRobin()
void WriteMaxUpstrokeVelocityMap(double threshold)
void WriteConductionVelocityMap(unsigned originNode, std::vector< double > distancesFromOriginNode)
void WriteVariablesOverTimeAtNodes(std::vector< unsigned > &rNodeIndices)
void WriteUpstrokeTimeMap(double threshold)
void WriteGenericFileToMeshalyzer(std::vector< std::vector< double > > &rDataPayload, const std::string &rFolder, const std::string &rFileName)
AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > & mrMesh
std::string ConvertToHdf5FriendlyString(double threshold)
void WriteApdMapFile(double repolarisationPercentage, double threshold)
void WriteOutputDataToHdf5(const std::vector< std::vector< double > > &rDataPayload, const std::string &rDatasetName, const std::string &rDatasetUnit, const std::string &rUnlimitedVariableName="PaceNumber", const std::string &rUnlimitedVariableUnit="dimensionless")
PostProcessingWriter(AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, const FileFinder &rDirectory, const std::string &rHdf5FileName, const std::string &rVoltageName="V", hsize_t hdf5DataWriterChunkSize=0)
void WriteAboveThresholdDepolarisationFile(double threshold)
PropagationPropertiesCalculator * mpCalculator