CEPS  24.01
Cardiac ElectroPhysiology Simulator
ActivationTracker.cpp
Go to the documentation of this file.
1 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2  This file is part of CEPS.
3 
4  CEPS is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  CEPS is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with CEPS (see file LICENSE at root of project).
16  If not, see <https://www.gnu.org/licenses/>.
17 
18 
19  Copyright 2019-2024 Inria, Universite de Bordeaux
20 
21  Authors, in alphabetical order:
22 
23  Pierre-Elliott BECUE, Florian CARO, Yves COUDIERE(*), Andjela DAVIDOVIC,
24  Charlie DOUANLA-LONTSI, Marc FUENTES, Mehdi JUHOOR, Michael LEGUEBE(*),
25  Pauline MIGERDITICHAN, Valentin PANNETIER(*), Nejib ZEMZEMI.
26  * : currently active authors
27 
28 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
35 
37  m_problem (nullptr),
38  m_geom (nullptr),
39  m_timeWriter (nullptr),
40  m_nbIterPostProcess (1),
41  m_activationTimeData ({}),
42  m_activationNotYetSeen({}),
43  m_activationSeen ({}),
44  m_foundPeak ({}),
45  m_foundAPD50 ({}),
46  m_foundAPD ({}),
47  m_activatedVolume ({}),
48  m_tissueVolume (0.),
49  m_nToBeSeen (0)
50 {
51 
53  "Cannot create post processing unit from nullptr solver link"
54  );
55 
56  m_problem = solver->getCardiacProblem();
57  m_geom = m_problem->getGeometry();
58  m_timeWriter = solver->getTimeWriter();
59 
60  if (ceps::isValidPtr(m_problem->getParameters()))
61  setupWithParameters(m_problem->getParameters());
62 
63  initializeActivationMap();
64 
65 }
66 
68 {
69  for (CepsUInt i=0; i<m_activationTimeData.size(); i++)
70  CEPS_SAYS("Fraction of activated tissue for activation data set #" << i << ": "
72 }
73 
74 void
76  CepsReal t,
77  DHVecPtr solution,
78  DHVecPtr prevSolution
79 )
80 {
81 
82  if (ceps::isProfiling())
83  m_profiler.start("actTrackUpdate","updating activation data");
84 
85  if (m_activationTimeData.empty() or iter%m_nbIterPostProcess!=0)
86  return;
87 
88  solution ->getLocalData();
89  prevSolution ->getLocalData();
90  m_activationTimes->getLocalData();
91  m_peakValues ->getLocalData();
92  m_peakTimes ->getLocalData();
93  m_APD50 ->getLocalData();
94  m_APD ->getLocalData();
95  CepsUInt count = 0;
96 
97  // Loop through parameter set in definition of activation times
98  for (auto it: m_activationTimeData)
99  {
100  // Access data
101 
102  CepsSet<CepsUInt> tmpSetOfSeens;
103  CepsReal threshold = m_activationTimeData[it.first][_threshold];
104  CepsReal apdpctg = m_activationTimeData[it.first][_apdpctg ];
105  CepsReal minV4Peak = m_activationTimeData[it.first][_minV4Peak];
106 
107  // A vector to compute currently activated volume
108  m_activated->getLocalData();
109 
110  // Activation time: check if threshold has been exceeded
111  // beware i is already in the form PROBLEM_DIM * j + s
112  for (auto dofId : m_activationNotYetSeen[it.first])
113  if ((*solution)[dofId] > threshold)
114  {
115  tmpSetOfSeens.insert(dofId);
116  (*m_activationTimes)[dofId] = t;
117  (*m_activated )[dofId] = 1.;
118  }
119 
120  // we have to seek peak and APD just among
121  // for dof which had pass activation
122  for (auto dofId : m_activationSeen[it.first])
123  {
124  if (not m_foundPeak[it.first].contains(dofId))
125  {
126  // v+ > v_step and dv / dt <0
127  if (((*solution)[dofId]>minV4Peak) and (*solution)[dofId] < (*prevSolution)[dofId])
128  {
129  m_foundPeak[it.first].insert(dofId);
130  (*m_peakValues)[dofId] = (*solution)[dofId];
131  (*m_peakTimes) [dofId] = t;
132  }
133  }
134  if ((*solution)[dofId]<threshold)
135  {
136  (*m_activated)[dofId] = 0.;
137  }
138  }
139 
140  // we have to seek peak and APD among dof which had pass activation
141  for (auto dofId : m_foundPeak[it.first])
142  {
143  auto testPercent = [&] (double x, double perc) {
144  double thres = ((*m_peakValues)[dofId]-threshold)*(1-perc)+threshold;
145  return (x < thres);
146  };
147  // we assume that m_apdpctg belongs to [0.5,1]
148  if (not m_foundAPD50[it.first].contains(dofId)
149  and testPercent((*solution)[dofId],0.5))
150  {
151  (*m_APD50)[dofId] = t;
152  m_foundAPD50[it.first].insert(dofId);
153  }
154 
155  if (m_foundAPD50[it.first].contains(dofId)
156  and not m_foundAPD[it.first].contains(dofId)
157  and testPercent((*solution)[dofId],apdpctg))
158  {
159  (*m_APD)[dofId] = t;
160  m_foundAPD[it.first].insert(dofId);
161  }
162  }
163 
164  m_activated->releaseLocalData();
165  {
166  auto fe = ceps::runtimeCast<FiniteElements*>(m_problem->getSpatialDiscretization());
167  FEIntegrator<FEMassAssembler> itg(fe,false,m_problem->getTissueAttributes(),{m_problem->getUnknown(it.first)});
169  }
170  count++;
171 
172  // update of seens
173  for (auto dofId: tmpSetOfSeens)
174  {
175  m_activationNotYetSeen[it.first].erase (dofId);
176  m_activationSeen [it.first].insert(dofId);
177  }
178  }
179 
180  solution ->releaseLocalData();
181  prevSolution ->releaseLocalData();
182  m_activationTimes->releaseLocalData();
183  m_peakValues ->releaseLocalData();
184  m_peakTimes ->releaseLocalData();
185  m_APD50 ->releaseLocalData();
186  m_APD ->releaseLocalData();
187 
188  if (ceps::isProfiling())
189  m_profiler.stop("actTrackUpdate");
190 }
191 
192 void
194 {
195  if (m_activationTimeData.empty())
196  return;
197 
198  CEPS_SAYS("Now saving activation maps");
199 
200  CepsString fileName = m_problem->getOutputFileBase()+"_APD";
201 
202  VtkWriter* w = ceps::getNew<VtkWriter>(m_problem->getSpatialDiscretization(),fileName,1,
205 
207  CepsVector<CepsString> namesAct,namesPV,namesPT,namesAPD50,namesAPD;
208  for (auto it: m_activationTimeData)
209  {
210  Unknown* u = m_problem->getUnknown(it.first);
211  us .push_back(u);
212  namesAct .push_back("Activation time (" + u->getName() + ")");
213  namesPV .push_back("Peak value (" + u->getName() + ")");
214  namesPT .push_back("Peak time (" + u->getName() + ")");
215  namesAPD50.push_back("APD50 (" + u->getName() + ")");
216  namesAPD .push_back("APD (" + u->getName() + ")");
217  }
218 
219  w->addScalarData(m_activationTimes,namesAct ,us);
220  w->addScalarData(m_peakValues ,namesPV ,us);
221  w->addScalarData(m_peakTimes ,namesPT ,us);
222  w->addScalarData(m_APD50 ,namesAPD50,us);
223  w->addScalarData(m_APD ,namesAPD ,us);
224  w->write();
225 
227 
228 }
229 
230 CepsBool
232 {
233  if (m_activationTimeData.empty())
234  return false;
235  return allSeen(m_activationSeen);
236 }
237 
238 CepsBool
240 {
241  if (m_activationTimeData.empty())
242  return false;
243  return allSeen(m_foundAPD);
244 }
245 
248 {
249  return "Activation data can be found in file " + m_problem->getOutputFileBase() + "_APD.*";
250 }
251 
254 {
255  return m_activatedVolume;
256 }
257 
258 void
260 {
261  if (p->hasKey("post processing perdiod"))
262  m_nbIterPostProcess = CepsUInt(p->getReal("post processing period")/m_problem->getTimeStep());
263 
264  if (p->hasKey("activation time data"))
265  {
266  CepsVector<CepsString> options = ceps::split(p->getString("activation time data"),",");
267  for (CepsString opts: options)
268  {
269  std::stringstream ss(opts);
271  "activation time data: unable to read unknown id from string " + opts
272  );
273  CepsReal3D adt = ceps::readVertex(ss,
274  "unable to read activation time data (3 real numbers) from input string " + opts
275  );
277  "activation time data : cannot find unknown with ID " + ceps::toString(uId)
278  );
279 
280  m_activationTimeData.insert(std::make_pair(uId,adt));
281  }
282  }
283 }
284 
285 void
287 {
288 
289  if (m_activationTimeData.empty())
290  return;
291 
292  // allocation for saving action potential markers
293  auto sd = m_problem->getSpatialDiscretization();
294  m_activationTimes = sd->newDofHaloVector();
295  m_activated = sd->newDofHaloVector();
296  m_peakValues = sd->newDofHaloVector();
297  m_peakTimes = sd->newDofHaloVector();
298  m_APD50 = sd->newDofHaloVector();
299  m_APD = sd->newDofHaloVector();
300 
301  // Fill with nans in order to indicate non computed values
302  m_activationTimes->fill(std::nan(""));
303  m_activated ->fill( 0.);
304  m_peakValues ->fill(std::nan(""));
305  m_peakTimes ->fill(std::nan(""));
306  m_APD50 ->fill(std::nan(""));
307  m_APD ->fill(std::nan(""));
308  m_activatedVolume.resize(m_activationTimeData.size(),0.);
309 
310  // Build sets of nodes with seen/unseen activation
311  // (all unseen for now)
312 
314  CepsUInt i=0;
315  for (auto it: m_activationTimeData)
316  {
317  Unknown* u = m_problem->getUnknown(it.first);
319  for (DegreeOfFreedom* dof: dofs)
320  if (dof->getUnknown()==u)
321  indices.insert(dof->getGlobalIndex());
322  m_activationNotYetSeen[it.first] = indices;
323  m_activationSeen [it.first] = {};
324  m_foundPeak [it.first] = {};
325  m_foundAPD50 [it.first] = {};
326  m_foundAPD [it.first] = {};
327  m_nToBeSeen += indices.size();
328  std::stringstream ss;
329  ss << "relative activated volume for activation time data set #" << i;
331  i++;
332  }
333 
334 
335  // Compute the tissue volume once and for all
336  auto fe = ceps::runtimeCast<FiniteElements*>(sd);
337  DHVecPtr tissue = sd->newDofHaloVector();
339  auto tAttrs = m_problem->getTissueAttributes();
340  CepsBool allTissue = tAttrs.empty() or (tAttrs.size()==1 and tAttrs.contains(-1));
341 
342  tissue->fill(0.);
343  tissue->getLocalData();
344  for (auto toParse: {dofs,hdofs})
345  for (auto dof: toParse)
346  if (dof->hasOneOfAttributes(tAttrs) or allTissue)
347  (*tissue)[dof->getGlobalIndex()] = 1.;
348  tissue->releaseLocalData();
349  {
350  FEIntegrator<FEMassAssembler> itg(fe,false,tAttrs,{m_problem->getTMVUnknowns()[0]});
351  m_tissueVolume = itg.integrate(tissue);
352  }
353 
354 }
355 
356 CepsBool
358 {
359  CepsUInt nbSeen = 0;
360  for (auto aset : arrays)
361  nbSeen += aset.second.size();
362 
363  CepsInt myAllSeen = (nbSeen==m_nToBeSeen) ? 1 : 0;
364  CepsInt gAllSeen = 0;
365  MPI_Allreduce(&myAllSeen,&gAllSeen,1,MPI_INT,MPI_SUM,ceps::getCommunicator());
366  return (CepsUInt)gAllSeen == ceps::getGridSize();
367 }
368 
#define CEPS_SAYS(message)
Writes a message in the debug log and in the terminal (stdio).
#define CEPS_ABORT_IF(condition, message)
Stops the execution with a message if condition is true. If testing is enabled, only throws a runtime...
std::basic_string< CepsChar > CepsString
C++ format string.
Definition: CepsTypes.hpp:128
CepsIndex CepsUnknownIndex
For unknowns.
Definition: CepsTypes.hpp:217
std::map< _Key, _Tp, _Compare, _Alloc > CepsMap
C++ map.
Definition: CepsTypes.hpp:196
std::set< _Type, _Compare, _Alloc > CepsSet
C++ set.
Definition: CepsTypes.hpp:209
std::vector< _Type, _Alloc > CepsVector
C++ vector.
Definition: CepsTypes.hpp:155
bool CepsBool
Booleans.
Definition: CepsTypes.hpp:124
std::make_unsigned_t< CepsInt > CepsUInt
Unsigned version on CepsInt.
Definition: CepsTypes.hpp:109
float CepsReal
Need single precision floating point.
Definition: CepsTypes.hpp:100
CepsArray3< CepsReal > CepsReal3D
Three real scalars, used like this for compatibility in polynomials.
Definition: CepsTypes.hpp:178
int32_t CepsInt
Need 32 bit integer.
Definition: CepsTypes.hpp:106
std::shared_ptr< DistributedHaloVector > DHVecPtr
Typedef for pointer on Distributed Halo CepsVector.
DistributedInfos< DegreeOfFreedom * > * getDistributedDofs() const
Get the stored Degree Of Freedom repartition.
CepsOutputFormat getOutputFormat() const
Tells if output is binary or ascii.
CepsString getOutputFileBase() const
Output file name includes the directory.
AbstractDiscretization * getSpatialDiscretization() const
Link to the spatial discretization (FE, FV, etc)
Unknown * getUnknown(const CepsString &label) const
Get an unknown by its name.
CepsBool writesGlobalIndices() const
Tells if global indices are written on top of solution.
CepsReal getTimeStep() const
pde time step
CepsBool allPointsHaveBeenActivated() const
Tells if all points have seen AP start (checked with activation threshold)
CardiacProblem * m_problem
Link to problem structure.
DHVecPtr m_activationTimes
Values of activation times at each point.
void writeActivationMap()
Writes activation times and probe points data.
CepsMap< CepsUnknownIndex, CepsSet< CepsDofGlobalIndex > > m_foundAPD
Flag map.
static constexpr CepsUInt _apdpctg
Index in activation data.
TimeWriter * m_timeWriter
Link to the solver's time writer, if needed...
~ActivationTracker()
Destructor.
DHVecPtr m_APD
length of AP (?)
CepsString getByeByeMessage() const
Message to be displayed at the end of computation.
DHVecPtr m_peakTimes
Times of max potential.
void update(CepsInt iter, CepsReal time, DHVecPtr solution, DHVecPtr prevSolution)
Writes the solution (optionnally currents) every m_nbIterSnapshot steps only. Also updates activation...
DHVecPtr m_APD50
length of AP to get to 50% depol
CepsMap< CepsUnknownIndex, CepsSet< CepsDofGlobalIndex > > m_foundPeak
Flag map.
void setupWithParameters(InputParameters *params)
Initializes attributes from text input.
ActivationTracker(CardiacSolver *solver)
Constructor.
CepsMap< CepsUnknownIndex, CepsSet< CepsDofGlobalIndex > > m_activationSeen
Flag map.
static constexpr CepsUInt _threshold
Index in activation data.
CepsUInt m_nbIterPostProcess
Output periodicity.
CepsMap< CepsUnknownIndex, CepsSet< CepsDofGlobalIndex > > m_activationNotYetSeen
Flag map.
DHVecPtr m_peakValues
Value of max potential.
CepsMap< CepsUnknownIndex, CepsSet< CepsDofGlobalIndex > > m_foundAPD50
Flag map.
CepsMap< CepsUnknownIndex, CepsReal3D > m_activationTimeData
AP analysis.
CepsUInt m_nToBeSeen
Number of dofs in tissue for each activation time data.
CepsReal m_tissueVolume
Reference volume.
DHVecPtr m_activated
1/0 status of point (above/below threshold)
static constexpr CepsUInt _minV4Peak
Index in activation data.
CepsBool allPointsHaveBeenRepolarized() const
Tells if all points have seen AP end (checked with APD percentage)
void initializeActivationMap()
Allocates arrays for activation detection.
CepsVector< CepsReal > m_activatedVolume
Size of tissue that is above activation threshold.
CepsVector< CepsReal > & getActivatedVolume()
Returns reference to data, so it can be linked to a TimeWriter.
CepsBool allSeen(const CepsMap< CepsUnknownIndex, CepsSet< CepsDofGlobalIndex >> &) const
Check not nans for given array.
virtual CepsVector< Unknown * > getTMVUnknowns() const =0
Returns a vector containing all unknowns that are a TMV (especially useful for bilayer)
const CepsSet< CepsAttribute > & getTissueAttributes() const
All attributes that localize tissue.
Solves cardiac problems, that all share the same structure.
static Profiler m_profiler
The same profiler for each big object.
Definition: CepsObject.hpp:72
A degree of freedom for any kind of problem The dof can be associated to a geometrical element or not...
const CepsVector< _Type > & getOwned() const
Get data owned by the processor, const.
const CepsVector< _Type > & getHalo() const
Get halo data owned by neighbor processor, const.
Computes the integral of a quantity on the whole domain or subdomains, using a FE matrix.
CepsReal integrate(DHVecPtr u, DMatPtr mat=nullptr)
Returns the value of the integral by computing ones dot Mu.
Reads and stores simulation configuration.
CepsBool hasKey(const keyType &key) const
Tells if key exists in input file.
CepsString getString(const keyType &key) const
Reads a CepsString from configuration.
CepsReal getReal(const keyType &key) const
Reads a floating point value from configuration.
void stop(CepsString lbl)
Stops the measure of a labeled chronometer.
void start(CepsString lbl, CepsString dspl="")
Creates or continue a labeled chronometer.
void addCustomData(const CepsString &name, CepsReal *data)
Add a custom data to be written in the outputfile. Custom data cannot be added after header has been ...
Definition: TimeWriter.cpp:72
A class used to defined an unknown of a PDE problem The unknown can be defined on a specific region,...
Definition: Unknown.hpp:45
const CepsString & getName() const
Get the name of the unknown.
Definition: Unknown.cpp:69
A class that enables the output of binary parallel VTK format files.
Definition: VtkWriter.hpp:69
void addScalarData(const DHVecPtr &v, const CepsVector< CepsString > &fieldNames, const CepsVector< Unknown * > &us)
Set multiple scalar fields to be output by this writer. addScalarData for several unknowns.
Definition: VtkWriter.cpp:101
void write(CepsReal time=0., CepsBool writeXmlEntry=true)
Write all stored data.
Definition: VtkWriter.cpp:176
CepsString toString(_Tp value)
Convert a given value to a string (input has to be compatible with std::to_string)
Definition: CepsString.hpp:409
MPI_Comm getCommunicator()
Get the communicator.
CepsBool isValidPtr(_Type *ptr)
Tells if pointer is not null.
Definition: CepsMemory.hpp:61
CepsUInt getGridSize()
Returns the number of process on the computing grid.
CepsReal3D readVertex(std::istream &file, const CepsString &errorMessage="")
Reads an integral number from an istream, aborts if conversion fails advances the stream by 1 word.
Definition: CepsString.cpp:693
CepsInt readInt(std::istream &file, const CepsString &errorMessage="")
Reads an integral number from an istream, aborts if conversion fails advances the stream by 1 word.
Definition: CepsString.cpp:677
CepsBool isNullPtr(_Type *ptr)
Tells if passed pointer is null.
Definition: CepsMemory.hpp:45
CepsVector< CepsString > split(const CepsString &s, const CepsString &delimiters=CepsString(" \t"))
Splits a string using mulitple delimiters in a single string.
Definition: CepsString.cpp:38
CepsBool isProfiling()
Check if we are currently profiling on the master proc (always false on slave procs).
Definition: CepsFlags.cpp:257
void destroyObject(_Type &)
Destroy[delete] any type.
Definition: CepsMemory.hpp:116