17 #include "petsc_utils.h"
29 log_msg(NULL, 5, 0,
"DREAM/Eikonal physics currently do not support MPI parallelization. Use openMP instead. Aborting!");
39 logger =
f_open(
"eikonal.log", param_globals::experiment != 4 ?
"w" :
"r");
63 param_globals::dt, 0,
"elec::ref_dt",
"TS");
87 log_msg(NULL, 0, 0,
"All done in %f sec.",
float(t2 - t1));
95 m->
regions.resize(param_globals::num_gregions);
98 log_msg(
logger, 0, 0,
"Setting up %s tissue poperties for %d regions ..", grid_name,
99 param_globals::num_gregions);
104 for (
size_t i = 0; i < m->
regions.size(); i++, reg++) {
105 if (!strcmp(param_globals::gregion[i].
name,
"")) {
106 snprintf(buf,
sizeof buf,
", gregion_%d",
int(i));
107 param_globals::gregion[i].name =
dupstr(buf);
110 reg->
regname = strdup(param_globals::gregion[i].
name);
112 reg->
nsubregs = param_globals::gregion[i].num_IDs;
118 for (
int j = 0; j < reg->
nsubregs; j++)
119 reg->
subregtags[j] = param_globals::gregion[i].ID[j];
123 elecMaterial* emat =
new elecMaterial();
126 emat->InVal[0] = param_globals::gregion[i].g_il;
127 emat->InVal[1] = param_globals::gregion[i].g_it;
128 emat->InVal[2] = param_globals::gregion[i].g_in;
130 emat->ExVal[0] = param_globals::gregion[i].g_el;
131 emat->ExVal[1] = param_globals::gregion[i].g_et;
132 emat->ExVal[2] = param_globals::gregion[i].g_en;
134 emat->BathVal[0] = param_globals::gregion[i].g_bath;
135 emat->BathVal[1] = param_globals::gregion[i].g_bath;
136 emat->BathVal[2] = param_globals::gregion[i].g_bath;
139 for (
int j = 0; j < 3; j++) {
140 emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
141 emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
142 emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
149 const char* file = g ==
Eikonal::intra_grid ? param_globals::gi_scale_vec : param_globals::ge_scale_vec;
155 void Eikonal::setup_mappings()
164 log_msg(
logger, 0, 0,
"%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
168 log_msg(
logger, 0, 0,
"%s: Setting up intracellular PETSc to canonical permutation.", __func__);
179 case EIKONAL: solve_EIKONAL();
break;
180 case DREAM: solve_DREAM();
break;
181 default: solve_RE();
break;
212 double curtime =
timing(t2, t1);
244 void Eikonal::setup_stimuli()
249 stimuli.resize(param_globals::num_stim);
250 for (
int i = 0; i < param_globals::num_stim; i++) {
254 if (param_globals::stim[i].crct.type != 0 && param_globals::stim[i].crct.type != 9) {
257 log_msg(NULL, 5, 0,
"%s error: stimulus of type %i is incompatible with the eikonal model! Use I_tm or Vm_clmp instead. Aborting!", __func__, s.
phys.
type);
269 log_msg(NULL, 2, 0,
"Only geometry, start time, npls, and bcl of stim[%i] are used", i);
271 if (param_globals::stim[i].pulse.dumpTrace &&
get_rank() == 0) {
280 void Eikonal::stimulate_intracellular()
290 if (param_globals::operator_splitting) {
300 *ps.tmp_i1 = *ps.IIon;
301 *ps.tmp_i1 -= *ps.Irhs;
306 ps.mass_i->mult(*ps.tmp_i1, *ps.Irhs);
308 *ps.Irhs = *ps.tmp_i1;
316 if (illum_vec == NULL) {
317 log_msg(0, 5, 0,
"Cannot apply illumination stim: global vector not present!");
332 void Eikonal::clamp_Vm()
340 void Eikonal::setup_output()
348 restr_i, param_globals::num_io_nodes > 0);
350 if (param_globals::dataout_i) {
359 if (strcmp(param_globals::dream.
output.idifffile,
"") != 0) {
366 if (strcmp(param_globals::dream.
output.idifffile,
"") != 0) {
374 if (param_globals::num_trace) {
376 open_trace(
ion.
miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
383 void Eikonal::dump_matrices()
385 std::string bsname = param_globals::dump_basename;
391 if (param_globals::parab_solve == 1) {
393 fn = bsname +
"_Ki_CN.bin";
396 fn = bsname +
"_Ki.bin";
399 fn = bsname +
"_Mi.bin";
413 val = std::nan(
"NaN");
427 s_unit =
stimuli[sidx].pulse.wave.f_unit;
432 void Eikonal::setup_solvers()
445 if (param_globals::dump2MatLab) dump_matrices();
449 void Eikonal::checkpointing()
458 snprintf(save_fnm,
sizeof save_fnm,
"%s.%s.roe", param_globals::write_statef, tsav_ext);
467 snprintf(save_fnm,
sizeof save_fnm,
"checkpoint.%.1f.roe", tm.time);
472 void Eikonal::solve_EIKONAL()
482 void Eikonal::solve_RE()
493 void Eikonal::solve_DREAM()
511 void Eikonal::solve_RD()
549 if (param_globals::output_level > 1)
log_msg(0, 0, 0,
"\n *** Initializing Eikonal Solver ***\n");
552 if (param_globals::dream.
output.debugNode >= 0) {
553 nodeData.
idX = param_globals::dream.output.debugNode;
555 snprintf(buf,
sizeof buf,
"node_%lld.dat",
static_cast<long long>(
nodeData.
idX));
585 switch (mesh.
type[0]) {
599 log_msg(0, 5, 0,
"Error: Type of element is not compatible with this version of the eikonal model. Use tetrahedra or triangles");
604 if (strlen(param_globals::start_statef) > 0) load_state_file();
606 create_node_to_node_graph();
608 translate_stim_to_eikonal();
610 precompute_squared_anisotropy_metric();
630 for (
size_t i = 0; i < rs.
size(); i++) {
631 rs[i].nsubregs = param_globals::imp_region[i].num_IDs;
632 rs[i].subregtags = param_globals::imp_region[i].ID;
633 for (
int j = 0; j < rs[i].nsubregs; j++) {
634 if (rs[i].subregtags[j] == -1 &&
get_rank() == 0)
635 log_msg(NULL, 3,
ECHO,
"Warning: not all %u IDs provided for imp_region[%u]!\n", rs[i].nsubregs, i);
638 if (rs.
size() == 1) {
647 #pragma omp parallel for schedule(dynamic)
648 for (
size_t i = 0; i < alg_nod.
size(); i++) {
650 int reg = regionIDs[i];
652 const auto& region_diff = param_globals::imp_region[reg].dream.Idiff;
653 const auto& region_rest = param_globals::imp_region[reg].dream.CVrest;
658 if (model ==
GAUSS) {
659 diff_cur[v].alpha_1 = region_diff.alpha_i[0];
660 diff_cur[v].alpha_2 = region_diff.alpha_i[1];
661 diff_cur[v].alpha_3 = region_diff.alpha_i[2];
662 diff_cur[v].beta_1 = region_diff.beta_i[0];
663 diff_cur[v].beta_2 = region_diff.beta_i[1];
664 diff_cur[v].beta_3 = region_diff.beta_i[2];
665 diff_cur[v].gamma_1 = region_diff.gamma_i[0];
666 diff_cur[v].gamma_2 = region_diff.gamma_i[1];
667 diff_cur[v].gamma_3 = region_diff.gamma_i[2];
670 diff_cur[v].tau_F = region_diff.tau_F;
671 diff_cur[v].V_th = region_diff.V_th;
677 denom_cvrest[v] = log(region_rest.rho) / region_rest.psi;
679 if (param_globals::output_level)
log_msg(NULL, 0, 0,
"Diffusion current and CV restitution initialized in %f sec.",
timing(t2, t1));
682 void eikonal_solver::translate_stim_to_eikonal()
689 std::vector<std::pair<SF_real, int>> stim_events;
690 stim_events.reserve(param_globals::num_stim * 8);
692 for (
int stim_idx = 0; stim_idx < stimuliRef->size(); ++stim_idx) {
693 const stimulus& s = (*stimuliRef)[stim_idx];
694 for (
int idx_pls = 0; idx_pls < s.
ptcl.
npls; ++idx_pls) {
696 stim_events.emplace_back(start_time, stim_idx);
701 std::sort(stim_events.begin(), stim_events.end(),
702 [](
auto& a,
auto& b) { return a.first < b.first; });
705 size_t total_nodes = 0;
706 for (
auto& ev : stim_events)
707 total_nodes += (*stimuliRef)[ev.second].electrode.vertices.size();
713 for (
auto& ev : stim_events) {
714 const stimulus& s = (*stimuliRef)[ev.second];
722 if (param_globals::output_level)
723 log_msg(NULL, 0, 0,
"Translating stimuli for eikonal model done in %f sec.",
timing(t2, t1));
726 void eikonal_solver::create_node_to_node_graph()
735 std::vector<std::vector<mesh_int_t>> thread_neighbors(
num_pts);
740 std::vector<char> mark(
num_pts, 0);
742 #pragma omp for schedule(dynamic)
743 for (
int point_idx = 0; point_idx <
num_pts; point_idx++) {
745 std::vector<mesh_int_t> neighbors;
749 for (
int eedsp = 0; eedsp < numNBElem; eedsp++) {
753 for (
int nndsp = 0; nndsp <
MESH_SIZE; nndsp++) {
756 if (nb == point_idx)
continue;
765 for (
int nb : neighbors) mark[nb] = 0;
768 thread_neighbors[point_idx] = std::move(neighbors);
773 for (
int i = 0; i <
num_pts; i++) {
780 #pragma omp parallel for schedule(static)
781 for (
int i = 0; i <
num_pts; i++) {
782 std::copy(thread_neighbors[i].begin(),
783 thread_neighbors[i].end(),
787 if (param_globals::output_level) {
788 log_msg(NULL, 0, 0,
"Node-to-node graph done in %f sec.",
timing(t2, t1));
792 void eikonal_solver::precompute_squared_anisotropy_metric()
799 S.resize(mesh.l_numelem);
800 CV_L.resize(mesh.l_numelem);
814 #pragma omp for schedule(static)
815 for (
int eidx = 0; eidx < mesh.l_numelem; eidx++) {
816 double vl = 0.0, vt = 0.0, vn = 0.0;
819 for (
int g = 0; g < param_globals::num_gregions; g++) {
820 const auto& reg = param_globals::gregion[g];
821 const auto& dream = reg.dream;
822 for (
int j = 0; j < reg.num_IDs; j++) {
823 if (mesh.tag[eidx] == reg.ID[j]) {
837 double AR_T2 = (vl / vt) * (vl / vt);
838 double AR_N2 = (vl / vn) * (vl / vn);
840 f.
x = mesh.fib[3 * eidx + 0];
841 f.
y = mesh.fib[3 * eidx + 1];
842 f.
z = mesh.fib[3 * eidx + 2];
845 s.
x = mesh.she[3 * eidx + 0];
846 s.
y = mesh.she[3 * eidx + 1];
847 s.
z = mesh.she[3 * eidx + 2];
865 if (param_globals::output_level)
866 log_msg(NULL, 0, 0,
"Anisotropy tensors precomputed in %f sec.",
timing(t2, t1));
878 std::vector<mesh_int_t> activeList;
879 activeList.reserve(
num_pts / 10);
880 std::vector<char> in_active(
num_pts, 0);
893 add_to_active(activeList, in_active, nb);
900 while (!activeList.empty() && niter <= param_globals::dream.fim.max_iter) {
901 const std::vector<mesh_int_t> activeVec = std::move(activeList);
906 std::vector<mesh_int_t> local_active;
907 local_active.reserve(64);
909 #pragma omp for schedule(dynamic)
910 for (
size_t i = 0; i < activeVec.size(); ++i) {
912 remove_from_active(in_active,
id);
917 #pragma omp atomic write
920 if (std::fabs(p - q) < param_globals::dream.fim.tol) {
926 #pragma omp atomic write
932 local_active.push_back(
id);
940 add_to_active(activeList, in_active, nb);
949 auto [minIt, maxIt] = std::minmax_element(
T_A.
begin(),
T_A.
end());
955 double* atc =
AT->
ptr();
956 #pragma omp parallel for
957 for (
size_t i = 0; i < alg_nod.
size(); ++i) {
959 atc[i] = (ta ==
inf ? -1.0 : ta);
964 auto dur =
timing(t1, t0);
983 double time2stop_eikonal = param_globals::dream.tau_inc;
984 float maxadvance =
user_globals::tm_manager->
time + param_globals::dream.tau_s + param_globals::dream.tau_inc + param_globals::dream.tau_max;
995 if (
List[indX] == 0)
continue;
997 q = compute_coherence(indX);
1002 if (q > maxadvance)
continue;
1004 if (abs(p - q) < param_globals::dream.fim.tol || (
num_changes[indX] > param_globals::dream.fim.max_iter) || (q ==
inf || p ==
inf)) {
1008 if (
List[indXNB] == 1) {
1015 qNB = compute_coherence(indXNB);
1021 bool ignore_L2_if_valid = node_is_valid && qNB > pNB &&
nReadded2List[indXNB] >= param_globals::dream.fim.max_addpt;
1023 if (node_is_valid && (
nReadded2List[indXNB] < param_globals::dream.fim.max_addpt) || ignore_L2_if_valid) {
1031 if (param_globals::dream.
output.debugNode == indXNB) {
1040 if (param_globals::dream.
output.debugNode == indX) {
1049 update_Ta_in_active_list();
1051 if (
actMIN > actMIN_old) {
1052 progress_time =
actMIN - actMIN_old;
1057 time2stop_eikonal -= progress_time;
1060 }
while (time2stop_eikonal > 0 &&
sum(
List) > 0);
1068 double* atc =
AT->
ptr();
1069 double* rpt =
RT->
ptr();
1070 for (
size_t i = 0; i < alg_nod.
size(); i++) {
1084 auto dur =
timing(t1, t0);
1093 if (param_globals::dream.
output.debugNode >= 0) {
1104 case 4:
return update_impl<4>(indX, CVrest_factor, isDREAM);
1105 case 3:
return update_impl<3>(indX, CVrest_factor, isDREAM);
1106 case 2:
return update_impl<2>(indX, CVrest_factor, isDREAM);
1123 std::array<SF::Point, N> base;
1124 std::array<double, N> values;
1125 std::array<int, N> nodeIDs;
1128 for (std::size_t j = 0; j < N; j++) {
1129 int n_i =
e2n_con[indEle + j];
1131 base[k].x = mesh.xyz[3 * n_i + 0]; base[k].y = mesh.xyz[3 * n_i + 1]; base[k].z = mesh.xyz[3 * n_i + 2];
1132 values[k] =
T_A[n_i];
1136 base[N - 1].x = mesh.xyz[3 * indX + 0]; base[N - 1].y = mesh.xyz[3 * indX + 1]; base[N - 1].z = mesh.xyz[3 * indX + 2];
1137 values[N - 1] =
T_A[indX];
1138 nodeIDs[N - 1] = indX;
1142 double cv =
CV_L[Elem_i] * CVrest_factor;
1143 if (cv == 0.0)
continue;
1147 if (!CheckValidity || !is_not_valid_update<N>(nodeIDs, time)) {
1148 LocalSolver<N> solver(D, base, values);
1150 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1159 if constexpr (N - 1 == 3) {
1161 for (
int i = 0; i < (N - 1); ++i) {
1162 for (
int j = i + 1; j < (N - 1); ++j) {
1164 std::array<int, 3> tri_ids{nodeIDs[i], nodeIDs[j], nodeIDs[N - 1]};
1165 if (is_not_valid_update<3>(tri_ids, time))
continue;
1167 std::array<SF::Point, 3> tri_pts{base[i], base[j], base[N - 1]};
1168 std::array<double, 3> tri_vals{values[i], values[j], values[N - 1]};
1170 LocalSolver<3> solver(D, tri_pts, tri_vals);
1173 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1181 for (
int i = 0; i < N - 1; i++) {
1182 std::array<int, 2> edge_ids{nodeIDs[i], nodeIDs[N - 1]};
1183 if (is_not_valid_update<2>(edge_ids, time))
continue;
1185 std::array<SF::Point, 2> edge_pts{base[i], base[N - 1]};
1186 std::array<double, 2> edge_vals{values[i], values[N - 1]};
1188 LocalSolver<2> solver(D, edge_pts, edge_vals);
1190 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1200 bool eikonal_solver::is_not_valid_update(
const std::array<int, N>& nodeIDs,
double time)
1202 const int target = nodeIDs[N - 1];
1203 const bool failedStim = (
stim_status[target] == 2);
1205 for (
int i = 0; i < N - 1; i++) {
1206 const int nb = nodeIDs[i];
1208 if (
T_A[nb] < time)
return true;
1209 if (
T_A[nb] <
T_R[nb])
return true;
1210 if (failedStim &&
stim_status[nb] == 1)
return true;
1219 const double delay = (
stim_status[indX] == 2) ? 5.0 : 0.0;
1223 (
T_R[indX] + delay) == -400.0 ||
1224 std::fabs(tmpTA -
TA_old[indX]) < param_globals::dream.fim.tol ||
1225 T_R[indX] > tmpTA) {
1230 const double DI = tmpTA - (
T_R[indX] + delay);
1235 const double Factor_DI = 1.0 -
rho_cvrest[indX] * std::exp(exponent);
1237 if (Factor_DI <= 0.0) {
1242 return (DI <=
theta_cvrest[indX]) ? -Factor_DI : Factor_DI;
1249 SF_real q = update(indX, scaling,
true);
1251 for (
int niter = 0; niter < param_globals::dream.fim.max_coh; ++niter) {
1252 scaling = compute_H(indX, p);
1253 q = update(indX, std::fabs(scaling),
true);
1255 if (std::fabs(p - q) < param_globals::dream.fim.tol)
1262 return (compute_H(indX, q) < 0.0) ?
T_A[indX] : q;
1265 void eikonal_solver::compute_bc()
1267 bool EmpList =
sum(
List) == 0;
1279 bool cond2add = add_node_neighbor_to_list(
T_R[indNode],
T_A[indNode], TimeSt);
1280 if (cond2add && compute_H(indNode, TimeSt) > 0) {
1281 T_A[indNode] = TimeSt;
1285 if (param_globals::dream.
output.debugNode == indNode) {
1290 for (
int ii =
n2n_dsp[indNode]; ii <
n2n_dsp[indNode + 1]; ii++) {
1292 if (
List[indXNB] == 0) {
1294 if (param_globals::dream.
output.debugNode == indXNB) {
1303 if (cond2add && compute_H(indNode, TimeSt) <= 0) {
1323 if (indNode == -1)
continue;
1326 bool cond2add = add_node_neighbor_to_list(
T_R[indNode],
T_A[indNode], TimeSt);
1327 if (cond2add && compute_H(indNode, TimeSt) > 0) {
1328 T_A[indNode] = TimeSt;
1332 if (param_globals::dream.
output.debugNode == indNode) {
1337 for (
int ii =
n2n_dsp[indNode]; ii <
n2n_dsp[indNode + 1]; ii++) {
1339 if (
List[indXNB] == 0) {
1341 if (param_globals::dream.
output.debugNode == indXNB) {
1357 if (cond2add && compute_H(indNode, TimeSt) <= 0) {
1373 bool act_is_in_safety_window, empty_list_and_illegal_stimulus, empty_stimulus, stimulus_is_in_safety_window;
1375 act_is_in_safety_window = (
actMIN > param_globals::dream.tau_s) && (
actMIN - time > param_globals::dream.tau_s);
1378 empty_list_and_illegal_stimulus = (
sum(
List) == 0) && (empty_stimulus || stimulus_is_in_safety_window);
1380 return act_is_in_safety_window || empty_list_and_illegal_stimulus;
1383 void eikonal_solver::update_Ta_in_active_list()
1385 bool First_in_List = 1;
1387 for (
size_t j = 0; j <
List.size(); j++) {
1388 if (
T_A[j] < 0)
continue;
1389 if (
List[j] == 1 && First_in_List) {
1404 for (
size_t j = 0; j <
List.size(); j++) {
1419 return ((
RT < newTA) && (newTA < oldTA)) ||
1420 ((oldTA <
RT) && (
RT < newTA));
1428 const double thresh = param_globals::dream.repol_time_thresh;
1432 for (
size_t i = 0; i < alg_nod.
size(); i++) {
1433 if (old_ptr[i] >= thresh && new_ptr[i] < thresh) {
1434 T_R[alg_nod[i]] = time;
1448 int max_threads = omp_get_max_threads();
1449 omp_set_num_threads(1);
1458 for (
int i = 0; i < miif->
N_IIF; i++) {
1459 if (!miif->
N_Nodes[i])
continue;
1471 double Vm_old = miif->
ldata[i][limpet::Vm][current];
1472 double Iion_old = miif->
ldata[i][limpet::Iion][current];
1473 double prev_R =
T_R[loc_nodal_idx];
1475 bool cond_2upd =
T_R[loc_nodal_idx] <=
T_A[loc_nodal_idx] &&
T_A[loc_nodal_idx] !=
inf && Vm_old > param_globals::dream.repol_time_thresh;
1478 double elapsed_time = 0;
1479 double prev_Vm = miif->
ldata[i][limpet::Vm][current];
1484 miif->
ldata[i][limpet::Vm][current] -= miif->
ldata[i][limpet::Iion][current] * param_globals::dt;
1485 if (miif->
ldata[i][limpet::Vm][current] < param_globals::dream.repol_time_thresh) {
1486 T_R[loc_nodal_idx] = time + elapsed_time;
1489 }
while (elapsed_time < 600);
1492 miif->
ldata[i][limpet::Vm][current] = Vm_old;
1493 miif->
ldata[i][limpet::Iion][current] = Iion_old;
1496 }
while (current < miif->N_Nodes[i]);
1501 omp_set_num_threads(max_threads);
1505 auto dur =
timing(t1, t0);
1521 for (
size_t j = 0; j < alg_nod.
size(); j++) {
1525 double TA =
T_A[loc_nodal_idx];
1526 if (!(time >= TA && (TA + 5) >= time &&
List[loc_nodal_idx] == 0))
1529 double dT = time - TA;
1530 auto& node =
diff_cur[loc_nodal_idx];
1532 switch (node.model) {
1534 double term1 = (dT - node.beta_1) / node.gamma_1;
1535 double term2 = (dT - node.beta_2) / node.gamma_2;
1536 double term3 = (dT - node.beta_3) / node.gamma_3;
1537 c[loc_petsc_idx] = node.alpha_1 * exp(-term1 * term1) + node.alpha_2 * exp(-term2 * term2) + node.alpha_3 * exp(-term3 * term3);
1541 double e_on = (dT >= 0.0) ? 1.0 : 0.0;
1542 double e_off = (v[loc_petsc_idx] < node.V_th) ? 1.0 : 0.0;
1543 c[loc_petsc_idx] = node.A_F / node.tau_F * exp(dT / node.tau_F) * e_on * e_off;
1558 FILE* file_writestate;
1559 char buffer_writestate[1024];
1560 snprintf(buffer_writestate,
sizeof buffer_writestate,
"%s.%s.roe.dat", param_globals::write_statef, tsav_ext);
1561 file_writestate = fopen(buffer_writestate,
"w");
1562 for (
size_t jjj = 0; jjj <
List.size(); jjj++) {
1563 fprintf(file_writestate,
"%lld %lld %lld %f %f %f %f \n",
1564 static_cast<long long>(
List[jjj]),
1570 fclose(file_writestate);
1574 void eikonal_solver::load_state_file()
1577 FILE* file_startstate;
1578 char buffer_startstate[strlen(param_globals::start_statef) + 10];
1580 snprintf(buffer_startstate,
sizeof buffer_startstate,
"%s.dat", param_globals::start_statef);
1581 file_startstate = fopen(buffer_startstate,
"r");
1583 if (file_startstate == NULL) {
1584 log_msg(NULL, 5, 0,
"Not able to open state file: %s", buffer_startstate);
1585 }
else if (param_globals::output_level) {
1586 log_msg(NULL, 0, 0,
"Open state file for eikonal model: %s", buffer_startstate);
1589 int ListVal, numChangesVal, numChanges2Val;
1590 float T_AVal, T_RVal, TA_oldVal, D_IVal, PCLVal;
1593 while (fscanf(file_startstate,
"%d %d %d %f %f %f %f", &ListVal, &numChangesVal, &numChanges2Val, &T_AVal, &T_RVal, &TA_oldVal, &D_IVal) == 7) {
1594 List[index] = ListVal;
1597 T_A[index] = T_AVal;
1598 T_R[index] = T_RVal;
1599 TA_old[index] = TA_oldVal;
1600 D_I[index] = D_IVal;
1603 if (param_globals::output_level)
log_msg(NULL, 0, 0,
"Number of nodes in active list: %i",
sum(
List));
1605 fclose(file_startstate);
1612 const char* h1 =
" ------ ---------- ---------- ------- ------- | List logic ----- -------- | Neighbor node ---- |";
1613 const char* h2 =
" cycle AT old AT RT DI | Status Entry Exit | ID AT |";
1616 log_msg(NULL, 3, 0,
"%s error: Could not open file %s in %s. Turning off logging.\n",
1626 if (!this->logger)
return;
1633 std::ostringstream oss_TA, oss_TA_, oss_TR, oss_DI, oss_nbnTA;
1634 oss_TA << this->
T_A;
1635 oss_TA_ << this->
T_A_;
1636 oss_TR << this->
T_R;
1637 oss_DI << this->
D_I;
1641 snprintf(cbuf,
sizeof cbuf,
"%7s %10s",
"-",
"-");
1643 snprintf(cbuf,
sizeof cbuf,
"%7lld %10s",
static_cast<long long>(this->
idXNB), oss_nbnTA.str().c_str());
1646 snprintf(abuf,
sizeof abuf,
"%6lld %10s %10s %7s %7s",
1647 static_cast<long long>(this->
cycle),
1648 oss_TA.str().c_str(), oss_TA_.str().c_str(), oss_TR.str().c_str(), oss_DI.str().c_str());
1651 unsigned char flag = cflg ?
ECHO : 0;
1652 log_msg(this->logger, 0, flag |
FLUSH |
NONL,
"%9.3f %s | %s | %s |\n", time, abuf, bbuf, cbuf);
1656 this->T_A_ = this->
T_A;
1657 this->T_A = std::numeric_limits<double>::quiet_NaN();
1658 this->T_R = std::numeric_limits<double>::quiet_NaN();
1659 this->D_I = std::numeric_limits<double>::quiet_NaN();
1661 this->nbn_T_A = std::numeric_limits<double>::quiet_NaN();
1668 const char* stat_str;
1669 const char* reas_str;
1690 template<
int MESH_SIZE>
1695 const double& u1 = values[0];
1697 if constexpr (MESH_SIZE == 2) {
1698 return tsitsiklis_update_line({x1,x2}, D, u1);
1700 }
else if constexpr (MESH_SIZE == 3) {
1702 const double& u2 = values[1];
1703 return tsitsiklis_update_triangle({x1,x2,x3}, D, {u1,u2});
1705 }
else if constexpr (MESH_SIZE == 4) {
1708 const double& u2 = values[1];
1709 const double& u3 = values[2];
1711 double u_tet = tsitsiklis_update_tetra({x1,x2,x3,x4}, D, {u1,u2,u3});
1713 u_tet = std::numeric_limits<double>::infinity();
1716 double u_face1 = tsitsiklis_update_triangle({x1, x2, x4}, D, {u1, u2});
1717 double u_face2 = tsitsiklis_update_triangle({x1, x3, x4}, D, {u1, u3});
1718 double u_face3 = tsitsiklis_update_triangle({x2, x3, x4}, D, {u2, u3});
1720 double u_tri =
std::min({u_face1, u_face2, u_face3});
1725 template <
int MESH_SIZE>
1728 const double& value)
1736 template <
int MESH_SIZE>
1737 double LocalSolver<MESH_SIZE>::tsitsiklis_update_triangle(
const std::array<SF::Point, 3>& base,
1739 const std::array<double, 2>& values)
1744 const double& u1 = values[0];
1745 const double& u2 = values[1];
1746 double result = std::numeric_limits<double>::infinity();
1759 double denominator = p11 - k * k;
1760 double sqrt_val = (p11 * p22 - p12 * p12) / denominator;
1762 if (denominator > 1e-12) {
1763 const double sqrt_val = (p11 * p22 - p12 * p12) / denominator;
1764 if (sqrt_val >= 0.0) {
1765 const double rhs = k * std::sqrt(sqrt_val);
1766 double alpha1 = -(p12 + rhs) / p11;
1767 double alpha2 = -(p12 - rhs) / p11;
1772 for (
double alpha : {alpha1, alpha2}) {
1773 SF::Point x_interp = x1 * alpha + x2 * (1.0 - alpha);
1776 double u3 = alpha * u1 + (1.0 - alpha) * u2 + norm_D;
1783 double u_edge1 = tsitsiklis_update_line({x1,x3}, D, u1);
1784 double u_edge2 = tsitsiklis_update_line({x2,x3}, D, u2);
1786 return std::min({result, u_edge1, u_edge2});
1789 template <
int MESH_SIZE>
1790 double LocalSolver<MESH_SIZE>::tsitsiklis_update_tetra(
const std::array<SF::Point, 4>& base,
1792 const std::array<double, 3>& values)
1799 const double& u1 = values[0];
1800 const double& u2 = values[1];
1801 const double& u3 = values[2];
1808 const double k1 = u1 - u3;
1809 const double k2 = u2 - u3;
1818 const double r21 = r12;
1821 const double r31 = r13;
1822 const double r32 = r23;
1824 const double A1 = k2 * r11 - k1 * r12;
1825 const double A2 = k2 * r21 - k1 * r22;
1826 const double B = k2 * r31 - k1 * r32;
1827 const double k = k1 - (A1 / A2) * k2;
1828 const SF::Point z1 = y1 - (A1 / A2) * y2;
1829 const SF::Point z2 = y3 - (B / A2) * y2;
1837 const double denominator = p11 - k*k;
1838 const double sqrt_val = (p11 * p22 - (p12 * p12)) / denominator;
1839 const double rhs = k * std::sqrt(sqrt_val);
1841 double alpha1 = -(p12 + rhs) / p11;
1842 double alpha2 = -(B + alpha1 * A1) / A2;
1845 const double EPS = 1e-16;
1846 if ((std::abs(A1) < EPS) && (std::abs(A2) < EPS)) {
1847 alpha1 = (r12 * r23 - r13 * r22) / (r11 * r22 - (r12 * r12));
1848 alpha2 = (r12 * r13 - r11 * r23) / (r11 * r22 - (r12 * r12));
1849 }
else if ((std::abs(A1) < EPS) && (std::abs(A2) > EPS)) {
1852 }
else if ((std::abs(A1) > EPS) && (std::abs(A2) < EPS)) {
1857 double alpha3 = 1 - alpha1 - alpha2;
1858 const SF::Point dist = x4 - (alpha1 * x1 + alpha2 * x2 + alpha3 * x3);
1860 if (alpha1 < -EPS || alpha2 < -EPS || alpha3 < -EPS ||
1861 alpha1 > 1.0+EPS || alpha2 > 1.0+EPS || alpha3 > 1.0+EPS) {
1862 return std::numeric_limits<double>::infinity();
opencarp::local_index_t mesh_int_t
opencarp::real_t SF_real
Global scalar type.
Basic utility structs and functions, mostly IO related.
virtual void write(const char *filename) const =0
virtual void release_ptr(S *&p)=0
virtual void add_scaled(const abstract_vector< T, S > &vec, S k)=0
overlapping_layout< T > pl
nodal parallel layout
vector< T > dsp
connectivity starting index of each element
vector< S > she
sheet direction
vector< elem_t > type
element type
size_t l_numpts
local number of points
size_t size() const
The current size of the vector.
void resize(size_t n)
Resize a vector.
const T * end() const
Pointer to the vector's end.
void assign(InputIterator s, InputIterator e)
Assign a memory range.
const T * begin() const
Pointer to the vector's start.
Represents the ionic model and plug-in (IMP) data structure.
const IonType & get_type() const
Gets this IMP's model type.
virtual void copy_SVs_from(IonIfBase &other, bool alloc)=0
Copies the state variables of an IMP.
void compute(node_index_t start, node_index_t end, GlobalData_t **data)
Perform ionic model computation for 1 time step.
Target get_target() const
node_count_t get_num_node() const
Gets the number of nodes handled by this IMP.
virtual IonIfBase * make_ion_if(Target target, node_count_t num_node, const std::vector< std::reference_wrapper< IonType >> &plugins) const =0
Generate an IonIf object from this type.
std::vector< IonIfBase * > IIF
array of IIF's
opencarp::sf_vec * gdata[NUM_IMP_DATA_TYPES]
data used by all IMPs
std::vector< IonTypeList > plugtypes
plugins types for each region
void dump_state(char *, float, opencarp::mesh_t gid, bool, unsigned int)
GlobalData_t *** ldata
data local to each IMP
int N_IIF
how many different IIF's
node_count_t * N_Nodes
#nodes for each IMP
node_index_t ** NodeLists
local partitioned node lists for each IMP stored
int timer_idx
the timer index received from the timer manager
FILE_SPEC logger
The logger of the physic, each physic should have one.
const char * name
The name of the physic, each physic should have one.
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
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
int check_quiescence(double tm, double dt)
check for quiescence
void output_initial_activations()
output one nodal vector of initial activation time
void init(sf_vec &vm, sf_vec &phie, int offset, enum physic_t=elec_phys)
initializes all datastructs after electric solver setup
int check_acts(double tm)
check activations at sim time tm
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 > T_R
void init_imp_region_properties()
Initializes diffusion current models and CV restitution parameters per mesh node.
SF::vector< mesh_int_t > n2e_dsp
SF::vector< SF_real > T_A
mesh_int_t Index_currStim
SF::vector< SF_real > TA_old
SF::vector< diffusion_current > diff_cur
void init()
Initialize vectors and variables in the eikonal_solver class.
SF::vector< mesh_int_t > e2n_con
bool determine_model_to_run(double &time)
Determine the next model to run in the alternation between RD and Eikonal.
SF::vector< mesh_int_t > stim_status
SF::vector< mesh_int_t > elem_start
SF::vector< mesh_int_t > StimulusPoints
SF::vector< SF_real > rho_cvrest
std::vector< double > CV_L
void save_eikonal_state(const char *tsav_ext)
Save the current state of variables related to the Eikonal simulation to a file to initialize a futur...
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< 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
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
SF::vector< SF_real > theta_cvrest
SF::vector< mesh_int_t > num_changes
std::vector< mesh_int_t > n2n_dsp
SF::vector< SF_real > StimulusTimes
SF::vector< SF_real > kappa_cvrest
SF::vector< mesh_int_t > n2e_cnt
void cycFIM()
Implementation of the cyclical fast iterative method used in step A of the DREAM model.
void update_repolarization_times(const Ionics &ion)
Estimates initial repolarization times (T_R) in Step D of DREAM.
eikonal_solver_stats stats
std::vector< SF::dmat< double > > S
void write_data()
write registered data to disk
void close_files_and_cleanup()
close file descriptors
void register_output(sf_vec *inp_data, const mesh_t inp_meshid, const int dpn, const char *name, const char *units, const SF::vector< mesh_int_t > *idx=NULL, bool elem_data=false)
Register a data vector for output.
sf_mat * rhs_parab
rhs matrix to solve parabolic
void rebuild_matrices(MaterialType *mtype, limpet::MULTI_IF &miif, FILE_SPEC logger)
void solve(sf_vec &phie_i)
sf_mat * mass_i
lumped for parabolic problem
sf_mat * lhs_parab
lhs matrix (CN) to solve parabolic
sf_vec * Vmv
global Vm vector
sf_vec * old_vm
older Vm needed for 2nd order dT
int write_trace()
write traces to file
stim_t type
type of stimulus
int npls
number of stimulus pulses
double pcl
pacing cycle length
double start
start time of protocol
sig::time_trace wave
wave form of stimulus pulse
stim_protocol ptcl
applied stimulation protocol used
stim_electrode electrode
electrode geometry
stim_pulse pulse
stimulus wave form
void translate(int id)
convert legacy definitions to new format
bool is_active() const
Return whether stim is active.
void setup(int idx)
Setup from a param stimulus index.
void dump_vtx_file(int idx)
Export the vertices to vtx file.
stim_physics phys
physics of stimulus
std::string name
label stimulus
double time_step
global reference time step
int add_eq_timer(double istart, double iend, int ntrig, double iintv, double idur, const char *iname, const char *poolname=nullptr)
Add a equidistant step timer to the array of timers.
Diffusion Reaction Eikonal Alternant Model (DREAM) based on the electrics physics class.
void transpose_connectivity(const vector< T > &a_cnt, const vector< T > &a_con, vector< T > &b_cnt, vector< T > &b_con)
Transpose CRS matrix graph A into B.
double inner_prod(const Point &a, const Point &b)
T sum(const vector< T > &vec)
Compute sum of a vector's entries.
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
void outer_prod(const Point &a, const Point &b, const double s, double *buff, const bool add=false)
T local_nodal_to_local_petsc(const meshdata< T, S > &mesh, int rank, T local_nodal)
void init_vector(SF::abstract_vector< T, S > **vec)
V clamp(const V val, const W start, const W end)
Clamp a value into an interval [start, end].
void cnt_from_dsp(const std::vector< T > &dsp, std::vector< T > &cnt)
Compute counts from displacements.
void dsp_from_cnt(const std::vector< T > &cnt, std::vector< T > &dsp)
Compute displacements from counts.
constexpr T min(T a, T b)
void dump_trace(MULTI_IF *MIIF, limpet::Real time)
void open_trace(MULTI_IF *MIIF, int n_traceNodes, int *traceNodes, int *label, opencarp::sf_mesh *imesh)
Set up ionic model traces at some global node numbers.
timer_manager * tm_manager
a manager for the various physics timers
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
int stimidx_from_timeridx(const SF::vector< stimulus > &stimuli, const int timer_id)
determine link between timer and stimulus
SF::scattering * get_scattering(const int from, const int to, const SF::SF_nbr nbr, const int dpn)
Get a scattering from the global scatter registry.
void read_el_scale_vec(const char *file, mesh_t mt, SF::vector< double > &el_scale, int &el_scale_dpn)
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
SF::scattering * register_scattering(const int from, const int to, const SF::SF_nbr nbr, const int dpn)
Register a scattering between to grids, or between algebraic and nodal representation of data on the ...
SF::scattering * get_permutation(const int mesh_id, const int perm_id, const int dpn)
Get the PETSC to canonical permutation scattering for a given mesh and number of dpn.
void region_mask(mesh_t meshspec, SF::vector< RegionSpecs > ®spec, SF::vector< int > ®ionIDs, bool mask_elem, const char *reglist, bool warn_on_default_tags)
classify elements/points as belonging to a region
SF::meshdata< mesh_int_t, mesh_real_t > sf_mesh
void apply_stim_to_vector(const stimulus &s, sf_vec &vec, bool add)
vec3< V > cross(const vec3< V > &a, const vec3< V > &b)
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
V dist(const vec3< V > &p1, const vec3< V > &p2)
void init_stim_info(void)
uses potential for stimulation
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
SF::scattering * register_permutation(const int mesh_id, const int perm_id, const int dpn)
Register a permutation between two orderings for a mesh.
void init_sv_gvec(gvec_data &GVs, limpet::MULTI_IF *miif, sf_vec &tmpl, igb_output_manager &output_manager)
void assemble_sv_gvec(gvec_data &gvecs, limpet::MULTI_IF *miif)
char * dupstr(const char *old_str)
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
mesh_t
The enum identifying the different meshes we might want to load.
void get_time(double &tm)
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
SF::abstract_vector< SF_int, SF_real > sf_vec
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
void setup_dataout(const int dataout, std::string dataout_vtx, mesh_t grid, SF::vector< mesh_int_t > *&restr, bool async, const hashmap::unordered_set< int > *output_tags)
const char * get_tsav_ext(double time)
V timing(V &t2, const V &t1)
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
#define ALG_TO_NODAL
Scatter algebraic to nodal.
Electrical stimulation functions.
description of materal properties in a mesh
SF::vector< RegionSpecs > regions
array with region params
SF::vector< double > el_scale
optionally provided per-element params scale
int el_scale_dpn
0=disabled, 1=isotropic scalar, 3=anisotropic (sl, st, sn) per element
region based variations of arbitrary material parameters
physMaterial * material
material parameter description
int nsubregs
#subregions forming this region
int * subregtags
FEM tags forming this region.
char * regname
name of region
double slvtime_A
total time in Step A
void log_stats(double time, bool cflg)
double minAT
minimum activation time in current solve
double maxAT
maximum activation time in current solve
void init_logger(const char *filename)
int activeList
number of nodes currently in list
void update_iter(const int curiter)
double slvtime_B
total time in Step B
double slvtime_D
total time in Step D
bool bc_status
boundary conditions were applied?
void update_cli(double time, bool cflg)
void log_stats(double tm, bool cflg)
void init_logger(const char *filename)
int calls
# calls for this interval, this is incremented externally
double tot_time
total time, this is incremented externally
void log_stats(double tm, bool cflg)
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.
void update_status(enum status s, enum reason r)