openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
electrics_eikonal.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: Apache-2.0
3 
12 #pragma once
13 
14 #ifndef _EIKONAL_H
15 #define _EIKONAL_H
16 
17 #include <array>
18 
19 #include "SF_globals.h"
20 #include "electrics.h"
21 #include "physics_types.h"
22 #include "sim_utils.h"
23 #include "stimulate.h"
24 
25 #include "sf_interface.h"
26 #include "timers.h"
27 #include "ionics.h"
28 #include "basics.h"
29 
30 namespace opencarp
31 {
36 struct node_stats {
37  enum status { in = 1,
38  out = 0 };
39  enum reason { none = 0,
40  nbn = 1,
41  conv = 2,
42  stim = 3 };
46  SF_real T_A = std::numeric_limits<double>::quiet_NaN();
47  SF_real nbn_T_A = std::numeric_limits<double>::quiet_NaN();
48  SF_real T_A_ = std::numeric_limits<double>::quiet_NaN();
49  SF_real T_R = std::numeric_limits<double>::quiet_NaN();
50  SF_real D_I = std::numeric_limits<double>::quiet_NaN();
51  const char* status = "out";
52  const char* reasonIn = "-";
53  const char* reasonOut = "-";
54 
55  FILE_SPEC logger = NULL;
56 
58  {
59  f_close(logger);
60  }
61 
62  void init_logger(const char* filename);
63  void log_stats(double tm, bool cflg);
64  void update_status(enum status s, enum reason r);
65 };
66 
68 {
69  public:
70  eikonal_solver() : stimuliRef(nullptr) {}
71 
72  const double inf = std::numeric_limits<double>::infinity();
73 
74  // data to dump
76 
77  // persistent handle for the detailed per-node debug logger (dream.output.detailed_debugNode),
78  // opened once in init() and closed in the destructor instead of per debug call
80 
81  // cached once in init() so LogEikonalCandidate call sites can skip building their
82  // arguments entirely when no debug node is configured, rather than allocating them
83  // just to have LogEikonalCandidate itself immediately discard them
84  bool detailed_debug_enabled = false;
85 
86  // Has to be kept in line with param_globals::eik_solve
87  enum Idiff_t { FOOT = 0,
88  GAUSS = 1 };
90 
93  SF_real A_F = 1;
105  };
106 
107  // enum classes used to define status related to stimulus of each node
108  enum class StimStatus : mesh_int_t {
109  None = 0, // no stimulus attempted
110  Success = 1, // successfully stimulated
111  Failed = 2, // stimulus attempted but unsuccessful: node not recovered
112  Subthreshold = 3 // node recovered, but its cell is predicted not to fire
113  };
114 
115  // A pulse captures only if the predicted upstroke starts within this time after its onset
116  static constexpr double STIM_CAPTURE_WINDOW = 30.0; // ms
117 
118  // Stimulus table rows identify a node's pulse (onset, strength, duration). With NO_PULSE as its
119  // row, predict_upstroke() integrates the node's cell without a pulse. This happens after a
120  // checkpoint load for a node whose non-capturing pulse lies before the save time: the
121  // checkpoint stores only the pulse onset, and the pulse's effect is already in the loaded
122  // cell state.
123  static constexpr size_t NO_PULSE = static_cast<size_t>(-1);
124 
125  // enum classes used to define the type of repolarization times to find
126  enum class RepolMode {
127  Normal,
128  FailedStim
129  };
130 
131  // classification of a freshly-converged node after post-sweep FIM verification
132  enum class VerifyResult {
133  Verified, // sweep T_A is consistent with converged-only evaluation
134  Overestimated, // sweep T_A was inflated; re-enter node with corrected value
135  Unresolved // converged-only evaluation yielded no activation time; a genuine
136  // conduction block and not-yet-settled neighbors are indistinguishable
137  // at this point, so the node is kept active either way
138  };
139 
140  // record produced by post-sweep Phase 1 verification of a freshly-converged node
141  struct VerifyEntry {
142  mesh_int_t indX; // global node index
143  SF_real ta_verified; // T_A from converged-only evaluation; inf if none could be formed
144  VerifyResult result; // convergence quality classification
145  };
146 
147  // Vectors
149  SF::vector<SF_real> T_A, T_R, TA_old, D_I; // Time of activation, time of repolarization, time of activation previously calculated, diastolic interval, final conduction velocity
150  SF::vector<mesh_int_t> List, num_changes, nReadded2List; // List of active nodes (L), number of changes per node to enter iterate in the list, number of times reentering the list per node
151  SF::vector<mesh_int_t> StimulusPoints; // Nodes that have initial stimulus
152  std::vector<bool> is_stimulus_target; // per node: does it appear anywhere in StimulusPoints?
153  SF::vector<StimStatus> stim_status; // status per node after stimulus
155  std::vector<long> capture_candidate; // per node: index into the stimulus rows whose capture the current compute_bc() call predicts (-1: none)
156  std::vector<size_t> last_noncapturing_row; // per node: stimulus table row of that pulse (NO_PULSE: none, or it lies before the loaded checkpoint)
157  SF::vector<SF_real> last_noncapturing_stim; // per node: onset of the last stimulus pulse that failed or was subthreshold at the node (-inf: none)
158  SF::vector<SF_real> rejected_TA, rejected_at; // per node: the T_A reject_non_firing_activations() last turned down and the RD time it did so (-inf: none). Within one RD step the verdict cannot change, so Phase 2A does not propose that T_A again
159  SF::vector<SF_real> captured_onset, captured_TA; // onset and T_A of the stimulus pulse that last captured the node (-inf: none); Idiff is suppressed there from the onset to T_A + 5 ms (solve_RD)
160  SF::vector<int> StimulusOrigIndex; // logical stimulus index (into *stimuliRef) for each row of the tables above
161  std::vector<bool> warned_failed_capture; // per logical stimulus: already warned that it failed to capture a target node?
162  std::vector<bool> warned_zero_strength; // per logical stimulus: already warned that its pulse.strength is 0?
163  std::vector<bool> stim_failed_now; // per node: did the current compute_bc() call fail to capture it?
164  std::vector<mesh_int_t> stim_failed_nodes; // the nodes stim_failed_now is set for, so clearing it costs one pass over those
165  SF::vector<SF_real> failed_stim_time, T_R_before_failed_stim; // per node: earliest failed stimulus still ahead of the clock (-inf: none) and T_R before it, see save_eikonal_state
166  bool warned_cycle_stall = false; // already warned that solve_DREAM's cycle loop made no progress?
167  bool warned_sweep_stall = false; // already warned that cycFIM's list sweep made no progress?
168  long long num_past_activations = 0; // activation times set before the current RD time (see check_past_activation)
169  size_t Index_currStim; // index of the row in the table of stimuli and points
170  mesh_int_t num_active_nodes = 0; // number of nodes in the active List
171  SF_real actMIN = std::numeric_limits<double>::quiet_NaN(), actMAX = std::numeric_limits<double>::quiet_NaN(), maxadvance = std::numeric_limits<double>::quiet_NaN(); // min/max T_A in active list (wavefront bounds); maxadvance = global FIM upper bound
172  SF_real loaded_save_time = -1.0; // Time at which checkpoint was saved (-1 if no checkpoint loaded)
173 
174  // mesh related data
175  mesh_int_t MESH_SIZE; // number of points per element
176  bool twoFib; // true if there is sheet information, false if there is only fiber orientation
177  size_t num_pts;
178  SF::vector<mesh_int_t> e2n_con; // Connectivity vector with nodes that belong to each element
179  SF::vector<mesh_int_t> elem_start; // For the i_th element elem_start[j] stores its starting position in the "connect" vector
180  std::vector<mesh_int_t> n2n_connect, n2n_dsp;
182 
183  // Element data storage for anisotropy and CV
184  std::vector<SF::dmat<double>> S;
185  std::vector<double> CV_L;
186 
187  // CV restitution parameters
189 
190  // export vectors
191  sf_vec* Idiff = nullptr;
192  sf_vec* AT = nullptr;
193  sf_vec* RT = nullptr;
194 
195  // eikonal solver statistics
197 
198  // enum classes used in the debugger file
199  enum class DebugCase {
200  ListIter,
201  ListReenter,
204  Stimulated,
211  Unknown
212  };
213 
214  // Which element a candidate activation time was formed on. SELECTED instead marks the
215  // local solver's chosen candidate, whose element is read from the contributor count.
216  enum class ElementCandidate {
217  SELECTED,
218  TETRAHEDRON,
219  TRIANGLE,
220  EDGE,
221  UNKNOWN
222  };
223 
225  {
226  if (Idiff) delete Idiff;
227  if (AT) delete AT;
228  if (RT) delete RT;
229  if (stimuliRef) delete stimuliRef;
231  }
232 
234  void init();
235 
244  bool should_solve_reaction_diffusion(double& time);
245 
247  void set_stimuli(SF::vector<stimulus>& stimuli) { stimuliRef = &stimuli; }
248 
263  void clean_list();
264 
277  void save_eikonal_state(const char* roe_fnm);
278 
295  void update_repolarization_times(const Ionics& ion, RepolMode mode);
296 
298  void cycFIM(const Ionics& ion);
299 
301  void FIM();
302 
319  void deep_debug_log(DebugCase debug_case, mesh_int_t index_ref = 0, mesh_int_t ind_target = 0);
320 
340  void LogEikonalCandidate(ElementCandidate type, mesh_int_t ind_target, const int* contributors, size_t num_contributors, mesh_real_t candidate_TA, mesh_real_t candidate_scaling_CV, int counter);
341 
357  void LogPhaseOneVerify(mesh_int_t indX, SF_real ta_sweep, SF_real ta_verified, VerifyResult result, bool forceConverge);
358 
384  void update_repolarization_times_from_rd(sf_vec& Vmv, sf_vec& Vmv_old, double time);
385 
429  void compute_diffusion_current(const double& time, sf_vec& vm);
430 
463 
464  private:
465  // stimulus pointer
466  SF::vector<stimulus>* stimuliRef = nullptr;
467 
477  void set_successful_stimulus(mesh_int_t node, SF_real TA);
478 
491  void predict_upstroke(const Ionics& ion, const std::vector<size_t>& rows, std::vector<SF_real>& t_up, const std::vector<SF_real>* idiff_TA = nullptr, std::vector<SF_real>* vm_end = nullptr);
492 
502  void reject_non_firing_activations(const Ionics& ion, std::vector<mesh_int_t>& converged);
503 
505  SF_real idiff_template(const diffusion_current& node, SF_real dT, SF_real vm) const;
506 
512  void check_past_activation(mesh_int_t indX);
513 
524  void set_active(mesh_int_t i);
525 
531  void clear_active(mesh_int_t i);
532 
544  void mask_untrusted(mesh_int_t i);
545 
552  void unmask_untrusted(mesh_int_t i);
553 
564  void set_unsuccessful_stimulus(mesh_int_t node, size_t stimIndex, SF_real stimTime);
565 
576  void warn_zero_strength_stimulus(int stim_idx, SF_real stimTime);
577 
586  void activate_neighbors(mesh_int_t& node);
587 
596  void compute_bc(const Ionics& ion, bool EmptyList, SF_real time_upper_bound);
597 
633  void precompute_squared_anisotropy_metric();
634 
635  // --- Small helpers for active list management ---
636  inline void add_to_active(std::vector<mesh_int_t>& active,
637  std::vector<char>& in_active,
638  mesh_int_t node)
639  {
640  if (!in_active[node]) {
641  in_active[node] = 1;
642  active.push_back(node);
643  }
644  }
645 
646  inline void remove_from_active(std::vector<char>& in_active, mesh_int_t node)
647  {
648  in_active[node] = 0;
649  }
650 
659  void update_Ta_in_active_list();
660 
706  SF_real compute_H(mesh_int_t& indX, SF_real& tmpTA);
707 
720  void record_D_I(mesh_int_t indX);
721 
752  SF_real compute_coherence(mesh_int_t& indX, bool convergedOnly = false);
753 
780  SF_real update(mesh_int_t& indX, SF_real CVrest_factor = 1.0, bool isDREAM = false, bool convergedOnly = false);
781 
815  template <int N>
816  SF_real update_impl(mesh_int_t& indX, SF_real CVrest_factor = 1.0, bool CheckValidity = false, bool convergedOnly = false);
817 
849  template <int N>
850  bool is_not_valid_update(const std::array<int, N>& nodeIDs, double time, int e_i, double cv, bool convergedOnly = false);
851 
859  bool add_node_neighbor_to_list(SF_real& RT, SF_real& oldTA, SF_real& newTA);
860 
862  void translate_stim_to_eikonal();
863 
890  void create_node_to_node_graph();
891 
893  void load_state_file();
894 };
895 
896 class Eikonal : public Basic_physic
897 {
898  public:
905  enum grid_t { intra_grid = 0,
909 
912 
915 
917  sf_vec* phie_dummy = nullptr;
918 
922 
925 
928  // Has to be kept in line with param_globals::eik_solve
929  enum eikonal_t { EIKONAL = 0,
930  REp = 1,
931  REm = 2,
932  DREAM = 3 };
934 
937 
940 
944 
945  bool do_output_eikonal = false;
946 
948 
953  {
954  name = "Eikonal";
955  }
956 
965  void initialize();
966 
967  void destroy();
968  void compute_step();
969  void output_step();
970 
971  inline void output_timings()
972  {
973  // since ionics are included in electrics, we substract ionic timings from electric timings
976 
977  // now we call the base class timings output for Electrics
979  // and then for ionics
981  }
982 
984 
986  double timer_val(const int timer_id);
987 
989  std::string timer_unit(const int timer_id);
990 
991  private:
993  void solve_EIKONAL();
995  void solve_RE();
997  void solve_DREAM();
999  void solve_RD();
1000 
1002  void setup_stimuli();
1004  void stimulate_intracellular();
1007  void clamp_Vm();
1008 
1010  void setup_solvers();
1011 
1013  void setup_mappings();
1014 
1016  void setup_output();
1017 
1019  void dump_matrices();
1020 
1021  void checkpointing();
1022 
1029  void set_elec_tissue_properties(MaterialType* mtype, Eikonal::grid_t g, FILE_SPEC logger);
1030 };
1031 
1032 template <int MESH_SIZE>
1034 {
1035  private:
1036  SF::dmat<double>& D;
1037  std::array<SF::Point, MESH_SIZE>& points;
1038  std::array<double, MESH_SIZE>& values;
1039 
1058  double tsitsiklis_update_line(const std::array<SF::Point, 2>& base,
1059  const SF::dmat<double>& D,
1060  const double& value);
1083  double tsitsiklis_update_triangle(const std::array<SF::Point, 3>& base,
1084  const SF::dmat<double>& D,
1085  const std::array<double, 2>& values);
1109  double tsitsiklis_update_tetra(const std::array<SF::Point, 4>& base,
1110  const SF::dmat<double>& D,
1111  const std::array<double, 3>& values);
1112 
1113  public:
1114  LocalSolver(SF::dmat<double>& D, std::array<SF::Point, MESH_SIZE>& points, std::array<double, MESH_SIZE>& values)
1115  : D(D), points(points), values(values) {}
1116 
1117  // This function performs the local solver, returning the solution value for the last point with reference to
1118  // point order of constructor input
1119  double solve();
1120 };
1121 
1122 template class LocalSolver<2>;
1123 
1124 template class LocalSolver<3>;
1125 
1126 template class LocalSolver<4>;
1127 
1128 } // namespace opencarp
1129 
1130 #endif
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:31
float mesh_real_t
Definition: SF_container.h:32
opencarp::real_t SF_real
Global scalar type.
Definition: SF_globals.h:30
Basic utility structs and functions, mostly IO related.
A vector storing arbitrary data.
Definition: SF_vector.h:28
The abstract physics interface we can use to trigger all physics.
Definition: physics_types.h:44
virtual void output_timings()
Definition: physics_types.h:61
FILE_SPEC logger
The logger of the physic, each physic should have one.
Definition: physics_types.h:49
const char * name
The name of the physic, each physic should have one.
Definition: physics_types.h:47
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
Eikonal()
Most of the initialization is done with initialize()
SF::vector< stimulus > stimuli
the electrical stimuli
parabolic_solver parab_solver
Solver for the parabolic bidomain equation.
MaterialType mtype[2]
the material types of intra_grid and extra_grid grids.
void destroy()
Currently we only need to close the file logger.
double timer_val(const int timer_id)
figure out current value of a signal linked to a given timer
sf_vec * phie_dummy
no elliptic solver needed, but we need a dummy for phie to use parabolic solver
eikonal_solver eik_solver
Solver for the eikonal equation.
LAT_detector lat
the activation time detector
gvec_data gvec
datastruct holding global IMP state variable output
generic_timing_stats IO_stats
grid_t
An electrics grid identifier to distinguish between intra and extra grids.
igb_output_manager output_manager_cycle
void initialize()
Initialize the Eikonal class.
igb_output_manager output_manager_time
class handling the igb output
LocalSolver(SF::dmat< double > &D, std::array< SF::Point, MESH_SIZE > &points, std::array< double, MESH_SIZE > &values)
std::vector< bool > stim_failed_now
SF::vector< SF_real > captured_onset
void FIM()
Standard fast iterative method to solve eikonal equation with active list approach.
void update_repolarization_times_from_rd(sf_vec &Vmv, sf_vec &Vmv_old, double time)
Updates node repolarization times based on transmembrane voltage crossing.
SF::vector< SF_real > StimulusDurations
SF::vector< SF_real > T_R
SF::vector< SF_real > StimulusStrengths
SF::vector< mesh_int_t > n2e_dsp
void save_eikonal_state(const char *roe_fnm)
Save the current state of variables related to the Eikonal simulation to a file to initialize a futur...
SF::vector< SF_real > T_A
SF::vector< SF_real > stim_strength
SF::vector< SF_real > last_noncapturing_stim
SF::vector< SF_real > captured_TA
SF::vector< SF_real > rejected_at
SF::vector< SF_real > TA_old
SF::vector< SF_real > T_R_before_failed_stim
SF::vector< diffusion_current > diff_cur
void init()
Initialize vectors and variables in the eikonal_solver class.
SF::vector< SF_real > stim_duration
SF::vector< mesh_int_t > e2n_con
SF::vector< SF_real > stim_start
SF::vector< mesh_int_t > elem_start
std::vector< bool > is_stimulus_target
SF::vector< mesh_int_t > StimulusPoints
SF::vector< SF_real > rho_cvrest
void cycFIM(const Ionics &ion)
Implementation of the cyclical fast iterative method used in step A of the DREAM model.
std::vector< double > CV_L
SF::vector< SF_real > denom_cvrest
void compute_diffusion_current(const double &time, sf_vec &vm)
Computes the stimulus-driven diffusion current at mesh nodes.
std::vector< size_t > last_noncapturing_row
std::vector< mesh_int_t > n2n_connect
void set_stimuli(SF::vector< stimulus > &stimuli)
Simple setter for stimulus vector.
SF::vector< mesh_int_t > e2n_cnt
SF::vector< mesh_int_t > n2e_con
SF::vector< StimStatus > stim_status
void clean_list()
Clean the list of nodes by resetting their status and tracking changes based on the time step of the ...
SF::vector< SF_real > D_I
SF::vector< mesh_int_t > nReadded2List
std::vector< bool > warned_zero_strength
std::vector< mesh_int_t > stim_failed_nodes
void LogPhaseOneVerify(mesh_int_t indX, SF_real ta_sweep, SF_real ta_verified, VerifyResult result, bool forceConverge)
Logs the outcome of post-sweep Phase 1 verification for a freshly-converged node.
static constexpr size_t NO_PULSE
void update_repolarization_times(const Ionics &ion, RepolMode mode)
Estimates repolarization times (T_R) via a short ionic-model-only integration.
SF::vector< SF_real > theta_cvrest
SF::vector< mesh_int_t > num_changes
static constexpr double STIM_CAPTURE_WINDOW
std::vector< mesh_int_t > n2n_dsp
SF::vector< SF_real > StimulusTimes
SF::vector< SF_real > failed_stim_time
SF::vector< SF_real > kappa_cvrest
SF::vector< mesh_int_t > n2e_cnt
SF::vector< SF_real > rejected_TA
SF::vector< int > StimulusOrigIndex
eikonal_solver_stats stats
void LogEikonalCandidate(ElementCandidate type, mesh_int_t ind_target, const int *contributors, size_t num_contributors, mesh_real_t candidate_TA, mesh_real_t candidate_scaling_CV, int counter)
Logs detailed information about an Eikonal candidate update for a specific node.
std::vector< long > capture_candidate
bool should_solve_reaction_diffusion(double &time)
Determine the next model to run in the alternation between RD and Eikonal.
void deep_debug_log(DebugCase debug_case, mesh_int_t index_ref=0, mesh_int_t ind_target=0)
Logs detailed debug information for a specific node in the Eikonal simulation.
std::vector< bool > warned_failed_capture
SF::vector< mesh_int_t > List
void init_imp_region_properties(FILE_SPEC logger)
Initializes diffusion current models and CV restitution parameters per mesh node.
std::vector< SF::dmat< double > > S
Tissue level electrics, main Electrics physics class.
Electrical ionics functions and LIMPET wrappers.
constexpr T min(T a, T b)
Definition: ion_type.h:18
@ intra_elec_msh
Definition: sf_interface.h:45
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
Definition: basics.cc:138
Basic physics types.
Interface to SlimFem.
Simulator-level utility execution control functions.
Electrical stimulation functions.
description of materal properties in a mesh
Definition: fem_types.h:106
for analysis of the computations done to solve the eikonal model
Definition: timers.h:39
File descriptor struct.
Definition: basics.h:121
for analysis of the #iterations to solve CG
Definition: timers.h:74
Struct used for debugging purposes.
const char * reasonOut
reason for list entry
SF_real T_R
repolarization time
void init_logger(const char *filename)
void log_stats(double tm, bool cflg)
SF_real D_I
diastolic interval
mesh_int_t idXNB
neighboring node index responsible for list entry
const char * reasonIn
reason for list entry
SF_real T_A_
previous activation time
SF_real T_A
current activation time
SF_real nbn_T_A
activation time of neighboring node
mesh_int_t cycle
DREAM cycle.
mesh_int_t idX
node index
void update_status(enum status s, enum reason r)