32 #include "petsc_utils.h"
44 log_msg(NULL, 5, 0,
"DREAM/Eikonal physics currently do not support MPI parallelization. Use openMP instead. Aborting!");
54 logger =
f_open(
"eikonal.log", param_globals::experiment != 4 ?
"w" :
"r");
78 param_globals::dt, 0,
"elec::ref_dt",
"TS");
102 log_msg(NULL, 0, 0,
"All done in %f sec.",
float(t2 - t1));
110 m->
regions.resize(param_globals::num_gregions);
113 log_msg(
logger, 0, 0,
"Setting up %s tissue poperties for %d regions ..", grid_name,
114 param_globals::num_gregions);
119 for (
size_t i = 0; i < m->
regions.size(); i++, reg++) {
120 if (!strcmp(param_globals::gregion[i].
name,
"")) {
121 snprintf(buf,
sizeof buf,
", gregion_%d",
int(i));
122 param_globals::gregion[i].name =
dupstr(buf);
125 reg->
regname = strdup(param_globals::gregion[i].
name);
127 reg->
nsubregs = param_globals::gregion[i].num_IDs;
133 for (
int j = 0; j < reg->
nsubregs; j++)
134 reg->
subregtags[j] = param_globals::gregion[i].ID[j];
138 elecMaterial* emat =
new elecMaterial();
141 emat->InVal[0] = param_globals::gregion[i].g_il;
142 emat->InVal[1] = param_globals::gregion[i].g_it;
143 emat->InVal[2] = param_globals::gregion[i].g_in;
145 emat->ExVal[0] = param_globals::gregion[i].g_el;
146 emat->ExVal[1] = param_globals::gregion[i].g_et;
147 emat->ExVal[2] = param_globals::gregion[i].g_en;
149 emat->BathVal[0] = param_globals::gregion[i].g_bath;
150 emat->BathVal[1] = param_globals::gregion[i].g_bath;
151 emat->BathVal[2] = param_globals::gregion[i].g_bath;
154 for (
int j = 0; j < 3; j++) {
155 emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
156 emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
157 emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
164 const char* file = g ==
Eikonal::intra_grid ? param_globals::gi_scale_vec : param_globals::ge_scale_vec;
170 void Eikonal::setup_mappings()
179 log_msg(
logger, 0, 0,
"%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
183 log_msg(
logger, 0, 0,
"%s: Setting up intracellular PETSc to canonical permutation.", __func__);
194 case EIKONAL: solve_EIKONAL();
break;
195 case DREAM: solve_DREAM();
break;
196 default: solve_RE();
break;
227 double curtime =
timing(t2, t1);
259 void Eikonal::setup_stimuli()
264 stimuli.resize(param_globals::num_stim);
265 for (
int i = 0; i < param_globals::num_stim; i++) {
269 if (param_globals::stim[i].crct.type != 0 && param_globals::stim[i].crct.type != 9) {
272 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);
284 log_msg(NULL, 2, 0,
"Only geometry, start time, npls, and bcl of stim[%i] are used", i);
286 if (param_globals::stim[i].pulse.dumpTrace &&
get_rank() == 0) {
295 void Eikonal::stimulate_intracellular()
305 if (param_globals::operator_splitting) {
315 *ps.tmp_i1 = *ps.IIon;
316 *ps.tmp_i1 -= *ps.Irhs;
321 ps.mass_i->mult(*ps.tmp_i1, *ps.Irhs);
323 *ps.Irhs = *ps.tmp_i1;
331 if (illum_vec == NULL) {
332 log_msg(0, 5, 0,
"Cannot apply illumination stim: global vector not present!");
347 void Eikonal::clamp_Vm()
355 void Eikonal::setup_output()
363 restr_i, param_globals::num_io_nodes > 0);
365 if (param_globals::dataout_i) {
374 if (strcmp(param_globals::dream.
output.idifffile,
"") != 0) {
381 if (strcmp(param_globals::dream.
output.idifffile,
"") != 0) {
389 if (param_globals::num_trace) {
391 open_trace(
ion.
miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
398 void Eikonal::dump_matrices()
400 std::string bsname = param_globals::dump_basename;
406 if (param_globals::parab_solve == 1) {
408 fn = bsname +
"_Ki_CN.bin";
411 fn = bsname +
"_Ki.bin";
414 fn = bsname +
"_Mi.bin";
428 val = std::nan(
"NaN");
442 s_unit =
stimuli[sidx].pulse.wave.f_unit;
447 void Eikonal::setup_solvers()
460 if (param_globals::dump2MatLab) dump_matrices();
464 void Eikonal::checkpointing()
473 snprintf(save_fnm,
sizeof save_fnm,
"%s.%s.roe", param_globals::write_statef, tsav_ext);
482 snprintf(save_fnm,
sizeof save_fnm,
"checkpoint.%.1f.roe", tm.time);
487 void Eikonal::solve_EIKONAL()
497 void Eikonal::solve_RE()
508 void Eikonal::solve_DREAM()
526 void Eikonal::solve_RD()
564 if (param_globals::output_level > 1)
log_msg(0, 0, 0,
"\n *** Initializing Eikonal Solver ***\n");
567 if (param_globals::dream.
output.debugNode >= 0) {
568 nodeData.
idX = param_globals::dream.output.debugNode;
570 snprintf(buf,
sizeof buf,
"node_%lld.dat",
static_cast<long long>(
nodeData.
idX));
600 switch (mesh.
type[0]) {
614 log_msg(0, 5, 0,
"Error: Type of element is not compatible with this version of the eikonal model. Use tetrahedra or triangles");
619 if (strlen(param_globals::start_statef) > 0) load_state_file();
621 create_node_to_node_graph();
623 translate_stim_to_eikonal();
625 precompute_squared_anisotropy_metric();
645 for (
size_t i = 0; i < rs.
size(); i++) {
646 rs[i].nsubregs = param_globals::imp_region[i].num_IDs;
647 rs[i].subregtags = param_globals::imp_region[i].ID;
648 for (
int j = 0; j < rs[i].nsubregs; j++) {
649 if (rs[i].subregtags[j] == -1 &&
get_rank() == 0)
650 log_msg(NULL, 3,
ECHO,
"Warning: not all %u IDs provided for imp_region[%u]!\n", rs[i].nsubregs, i);
653 if (rs.
size() == 1) {
660 #pragma omp parallel for schedule(dynamic)
661 for (
int v = 0; v < mesh.
l_numpts; v++) {
662 int reg = regionIDs[v];
664 const auto& region_diff = param_globals::imp_region[reg].dream.Idiff;
665 const auto& region_rest = param_globals::imp_region[reg].dream.CVrest;
670 if (model ==
GAUSS) {
671 diff_cur[v].alpha_1 = region_diff.alpha_i[0];
672 diff_cur[v].alpha_2 = region_diff.alpha_i[1];
673 diff_cur[v].alpha_3 = region_diff.alpha_i[2];
674 diff_cur[v].beta_1 = region_diff.beta_i[0];
675 diff_cur[v].beta_2 = region_diff.beta_i[1];
676 diff_cur[v].beta_3 = region_diff.beta_i[2];
677 diff_cur[v].gamma_1 = region_diff.gamma_i[0];
678 diff_cur[v].gamma_2 = region_diff.gamma_i[1];
679 diff_cur[v].gamma_3 = region_diff.gamma_i[2];
682 diff_cur[v].tau_F = region_diff.tau_F;
683 diff_cur[v].V_th = region_diff.V_th;
689 denom_cvrest[v] = log(region_rest.rho) / region_rest.psi;
691 if (param_globals::output_level)
log_msg(NULL, 0, 0,
"Diffusion current and CV restitution initialized in %f sec.",
timing(t2, t1));
694 void eikonal_solver::translate_stim_to_eikonal()
701 std::vector<std::pair<SF_real, int>> stim_events;
702 stim_events.reserve(param_globals::num_stim * 8);
704 for (
int stim_idx = 0; stim_idx < stimuliRef->size(); ++stim_idx) {
705 const stimulus& s = (*stimuliRef)[stim_idx];
706 for (
int idx_pls = 0; idx_pls < s.
ptcl.
npls; ++idx_pls) {
708 stim_events.emplace_back(start_time, stim_idx);
713 std::sort(stim_events.begin(), stim_events.end(),
714 [](
auto& a,
auto& b) { return a.first < b.first; });
717 size_t total_nodes = 0;
718 for (
auto& ev : stim_events)
719 total_nodes += (*stimuliRef)[ev.second].electrode.vertices.size();
725 for (
auto& ev : stim_events) {
726 const stimulus& s = (*stimuliRef)[ev.second];
734 if (param_globals::output_level)
735 log_msg(NULL, 0, 0,
"Translating stimuli for eikonal model done in %f sec.",
timing(t2, t1));
738 void eikonal_solver::create_node_to_node_graph()
747 std::vector<std::vector<mesh_int_t>> thread_neighbors(
num_pts);
752 std::vector<char> mark(
num_pts, 0);
754 #pragma omp for schedule(dynamic)
755 for (
int point_idx = 0; point_idx <
num_pts; point_idx++) {
757 std::vector<mesh_int_t> neighbors;
761 for (
int eedsp = 0; eedsp < numNBElem; eedsp++) {
765 for (
int nndsp = 0; nndsp <
MESH_SIZE; nndsp++) {
768 if (nb == point_idx)
continue;
777 for (
int nb : neighbors) mark[nb] = 0;
780 thread_neighbors[point_idx] = std::move(neighbors);
785 for (
int i = 0; i <
num_pts; i++) {
792 #pragma omp parallel for schedule(static)
793 for (
int i = 0; i <
num_pts; i++) {
794 std::copy(thread_neighbors[i].begin(),
795 thread_neighbors[i].end(),
799 if (param_globals::output_level) {
800 log_msg(NULL, 0, 0,
"Node-to-node graph done in %f sec.",
timing(t2, t1));
804 void eikonal_solver::precompute_squared_anisotropy_metric()
811 S.resize(mesh.l_numelem);
812 CV_L.resize(mesh.l_numelem);
826 #pragma omp for schedule(static)
827 for (
int eidx = 0; eidx < mesh.l_numelem; eidx++) {
828 double vl = 0.0, vt = 0.0, vn = 0.0;
831 for (
int g = 0; g < param_globals::num_gregions; g++) {
832 const auto& reg = param_globals::gregion[g];
833 const auto& dream = reg.dream;
834 for (
int j = 0; j < reg.num_IDs; j++) {
835 if (mesh.tag[eidx] == reg.ID[j]) {
849 double AR_T2 = (vl / vt) * (vl / vt);
850 double AR_N2 = (vl / vn) * (vl / vn);
852 f.
x = mesh.fib[3 * eidx + 0];
853 f.
y = mesh.fib[3 * eidx + 1];
854 f.
z = mesh.fib[3 * eidx + 2];
857 s.
x = mesh.she[3 * eidx + 0];
858 s.
y = mesh.she[3 * eidx + 1];
859 s.
z = mesh.she[3 * eidx + 2];
877 if (param_globals::output_level)
878 log_msg(NULL, 0, 0,
"Anisotropy tensors precomputed in %f sec.",
timing(t2, t1));
890 std::vector<mesh_int_t> activeList;
891 activeList.reserve(
num_pts / 10);
892 std::vector<char> in_active(
num_pts, 0);
905 add_to_active(activeList, in_active, nb);
912 while (!activeList.empty() && niter <= param_globals::dream.fim.max_iter) {
913 const std::vector<mesh_int_t> activeVec = std::move(activeList);
918 std::vector<mesh_int_t> local_active;
919 local_active.reserve(64);
921 #pragma omp for schedule(dynamic)
922 for (
size_t i = 0; i < activeVec.size(); ++i) {
924 remove_from_active(in_active,
id);
929 #pragma omp atomic write
932 if (std::fabs(p - q) < param_globals::dream.fim.tol) {
938 #pragma omp atomic write
944 local_active.push_back(
id);
952 add_to_active(activeList, in_active, nb);
961 auto [minIt, maxIt] = std::minmax_element(
T_A.
begin(),
T_A.
end());
966 double* atc =
AT->
ptr();
968 #pragma omp parallel for simd
970 atc[i] = (t_a[i] ==
inf ? -1.0 : t_a[i]);
974 auto dur =
timing(t1, t0);
993 double time2stop_eikonal = param_globals::dream.tau_inc;
994 float maxadvance =
user_globals::tm_manager->
time + param_globals::dream.tau_s + param_globals::dream.tau_inc + param_globals::dream.tau_max;
1005 if (
List[indX] == 0)
continue;
1007 q = compute_coherence(indX);
1012 if (q > maxadvance)
continue;
1014 if (abs(p - q) < param_globals::dream.fim.tol || (
num_changes[indX] > param_globals::dream.fim.max_iter) || (q ==
inf || p ==
inf)) {
1018 if (
List[indXNB] == 1) {
1025 qNB = compute_coherence(indXNB);
1031 bool ignore_L2_if_valid = node_is_valid && qNB > pNB &&
nReadded2List[indXNB] >= param_globals::dream.fim.max_addpt;
1033 if (node_is_valid && (
nReadded2List[indXNB] < param_globals::dream.fim.max_addpt) || ignore_L2_if_valid) {
1041 if (param_globals::dream.
output.debugNode == indXNB) {
1050 if (param_globals::dream.
output.debugNode == indX) {
1059 update_Ta_in_active_list();
1061 if (
actMIN > actMIN_old) {
1062 progress_time =
actMIN - actMIN_old;
1067 time2stop_eikonal -= progress_time;
1070 }
while (time2stop_eikonal > 0 &&
sum(
List) > 0);
1077 double* atc =
AT->
ptr();
1078 double* rpt =
RT->
ptr();
1092 auto dur =
timing(t1, t0);
1101 if (param_globals::dream.
output.debugNode >= 0) {
1112 case 4:
return update_impl<4>(indX, CVrest_factor, isDREAM);
1113 case 3:
return update_impl<3>(indX, CVrest_factor, isDREAM);
1114 case 2:
return update_impl<2>(indX, CVrest_factor, isDREAM);
1131 std::array<SF::Point, N> base;
1132 std::array<double, N> values;
1133 std::array<int, N> nodeIDs;
1136 for (std::size_t j = 0; j < N; j++) {
1137 int n_i =
e2n_con[indEle + j];
1139 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];
1140 values[k] =
T_A[n_i];
1144 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];
1145 values[N - 1] =
T_A[indX];
1146 nodeIDs[N - 1] = indX;
1150 double cv =
CV_L[Elem_i] * CVrest_factor;
1151 if (cv == 0.0)
continue;
1155 if (!CheckValidity || !is_not_valid_update<N>(nodeIDs, time)) {
1156 LocalSolver<N> solver(D, base, values);
1158 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1167 if constexpr (N - 1 == 3) {
1169 for (
int i = 0; i < (N - 1); ++i) {
1170 for (
int j = i + 1; j < (N - 1); ++j) {
1172 std::array<int, 3> tri_ids{nodeIDs[i], nodeIDs[j], nodeIDs[N - 1]};
1173 if (is_not_valid_update<3>(tri_ids, time))
continue;
1175 std::array<SF::Point, 3> tri_pts{base[i], base[j], base[N - 1]};
1176 std::array<double, 3> tri_vals{values[i], values[j], values[N - 1]};
1178 LocalSolver<3> solver(D, tri_pts, tri_vals);
1181 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1189 for (
int i = 0; i < N - 1; i++) {
1190 std::array<int, 2> edge_ids{nodeIDs[i], nodeIDs[N - 1]};
1191 if (is_not_valid_update<2>(edge_ids, time))
continue;
1193 std::array<SF::Point, 2> edge_pts{base[i], base[N - 1]};
1194 std::array<double, 2> edge_vals{values[i], values[N - 1]};
1196 LocalSolver<2> solver(D, edge_pts, edge_vals);
1198 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1208 bool eikonal_solver::is_not_valid_update(
const std::array<int, N>& nodeIDs,
double time)
1210 const int target = nodeIDs[N - 1];
1211 const bool failedStim = (
stim_status[target] == 2);
1213 for (
int i = 0; i < N - 1; i++) {
1214 const int nb = nodeIDs[i];
1216 if (
T_A[nb] < time)
return true;
1217 if (
T_A[nb] <
T_R[nb])
return true;
1218 if (failedStim &&
stim_status[nb] == 1)
return true;
1227 const double delay = (
stim_status[indX] == 2) ? 5.0 : 0.0;
1231 (
T_R[indX] + delay) == -400.0 ||
1232 std::fabs(tmpTA -
TA_old[indX]) < param_globals::dream.fim.tol ||
1233 T_R[indX] > tmpTA) {
1238 const double DI = tmpTA - (
T_R[indX] + delay);
1243 const double Factor_DI = 1.0 -
rho_cvrest[indX] * std::exp(exponent);
1245 if (Factor_DI <= 0.0) {
1250 return (DI <=
theta_cvrest[indX]) ? -Factor_DI : Factor_DI;
1257 SF_real q = update(indX, scaling,
true);
1259 for (
int niter = 0; niter < param_globals::dream.fim.max_coh; ++niter) {
1260 scaling = compute_H(indX, p);
1261 q = update(indX, std::fabs(scaling),
true);
1263 if (std::fabs(p - q) < param_globals::dream.fim.tol)
1270 return (compute_H(indX, q) < 0.0) ?
T_A[indX] : q;
1273 void eikonal_solver::compute_bc()
1275 bool EmpList =
sum(
List) == 0;
1287 bool cond2add = add_node_neighbor_to_list(
T_R[indNode],
T_A[indNode], TimeSt);
1288 if (cond2add && compute_H(indNode, TimeSt) > 0) {
1289 T_A[indNode] = TimeSt;
1293 if (param_globals::dream.
output.debugNode == indNode) {
1298 for (
int ii =
n2n_dsp[indNode]; ii <
n2n_dsp[indNode + 1]; ii++) {
1300 if (
List[indXNB] == 0) {
1302 if (param_globals::dream.
output.debugNode == indXNB) {
1311 if (cond2add && compute_H(indNode, TimeSt) <= 0) {
1331 if (indNode == -1)
continue;
1334 bool cond2add = add_node_neighbor_to_list(
T_R[indNode],
T_A[indNode], TimeSt);
1335 if (cond2add && compute_H(indNode, TimeSt) > 0) {
1336 T_A[indNode] = TimeSt;
1340 if (param_globals::dream.
output.debugNode == indNode) {
1345 for (
int ii =
n2n_dsp[indNode]; ii <
n2n_dsp[indNode + 1]; ii++) {
1347 if (
List[indXNB] == 0) {
1349 if (param_globals::dream.
output.debugNode == indXNB) {
1365 if (cond2add && compute_H(indNode, TimeSt) <= 0) {
1381 bool act_is_in_safety_window, empty_list_and_illegal_stimulus, empty_stimulus, stimulus_is_in_safety_window;
1383 act_is_in_safety_window = (
actMIN > param_globals::dream.tau_s) && (
actMIN - time > param_globals::dream.tau_s);
1386 empty_list_and_illegal_stimulus = (
sum(
List) == 0) && (empty_stimulus || stimulus_is_in_safety_window);
1388 return act_is_in_safety_window || empty_list_and_illegal_stimulus;
1391 void eikonal_solver::update_Ta_in_active_list()
1393 bool First_in_List = 1;
1395 for (
size_t j = 0; j <
List.size(); j++) {
1396 if (
T_A[j] < 0)
continue;
1397 if (
List[j] == 1 && First_in_List) {
1412 for (
size_t j = 0; j <
List.size(); j++) {
1427 return ((
RT < newTA) && (newTA < oldTA)) ||
1428 ((oldTA <
RT) && (
RT < newTA));
1436 const double thresh = param_globals::dream.repol_time_thresh;
1438 for (
int ind_nodes = 0; ind_nodes <
num_pts; ind_nodes++) {
1439 if (old_ptr[ind_nodes] >= thresh && new_ptr[ind_nodes] < thresh) {
1440 T_R[ind_nodes] = time;
1454 int max_threads = omp_get_max_threads();
1455 omp_set_num_threads(1);
1463 for (
int i = 0; i < miif->
N_IIF; i++) {
1464 if (!miif->
N_Nodes[i])
continue;
1473 int ind_gb = (miif->
NodeLists[i][current]);
1476 double Vm_old = miif->
ldata[i][limpet::Vm][current];
1477 double Iion_old = miif->
ldata[i][limpet::Iion][current];
1478 double prev_R =
T_R[ind_gb];
1480 bool cond_2upd =
T_R[ind_gb] <=
T_A[ind_gb] &&
T_A[ind_gb] !=
inf && Vm_old > param_globals::dream.repol_time_thresh;
1483 double elapsed_time = 0;
1484 double prev_Vm = miif->
ldata[i][limpet::Vm][current];
1489 miif->
ldata[i][limpet::Vm][current] -= miif->
ldata[i][limpet::Iion][current] * param_globals::dt;
1490 if (miif->
ldata[i][limpet::Vm][current] < param_globals::dream.repol_time_thresh) {
1491 T_R[ind_gb] = time + elapsed_time;
1494 }
while (elapsed_time < 600);
1497 miif->
ldata[i][limpet::Vm][current] = Vm_old;
1498 miif->
ldata[i][limpet::Iion][current] = Iion_old;
1501 }
while (current < miif->N_Nodes[i]);
1506 omp_set_num_threads(max_threads);
1510 auto dur =
timing(t1, t0);
1526 for (
size_t j = 0; j < alg_nod.
size(); j++) {
1530 double TA =
T_A[loc_nodal_idx];
1531 if (!(time >= TA && (TA + 5) >= time &&
List[loc_nodal_idx] == 0))
1534 double dT = time - TA;
1535 auto& node =
diff_cur[loc_nodal_idx];
1537 switch (node.model) {
1539 double term1 = (dT - node.beta_1) / node.gamma_1;
1540 double term2 = (dT - node.beta_2) / node.gamma_2;
1541 double term3 = (dT - node.beta_3) / node.gamma_3;
1542 c[loc_petsc_idx] = node.alpha_1 * exp(-term1 * term1) + node.alpha_2 * exp(-term2 * term2) + node.alpha_3 * exp(-term3 * term3);
1546 double e_on = (dT >= 0.0) ? 1.0 : 0.0;
1547 double e_off = (v[loc_petsc_idx] < node.V_th) ? 1.0 : 0.0;
1548 c[loc_petsc_idx] = node.A_F / node.tau_F * exp(dT / node.tau_F) * e_on * e_off;
1563 FILE* file_writestate;
1564 char buffer_writestate[1024];
1565 snprintf(buffer_writestate,
sizeof buffer_writestate,
"%s.%s.roe.dat", param_globals::write_statef, tsav_ext);
1566 file_writestate = fopen(buffer_writestate,
"w");
1567 for (
size_t jjj = 0; jjj <
List.size(); jjj++) {
1568 fprintf(file_writestate,
"%lld %lld %lld %f %f %f %f \n",
1569 static_cast<long long>(
List[jjj]),
1575 fclose(file_writestate);
1579 void eikonal_solver::load_state_file()
1582 FILE* file_startstate;
1583 char buffer_startstate[strlen(param_globals::start_statef) + 10];
1585 snprintf(buffer_startstate,
sizeof buffer_startstate,
"%s.dat", param_globals::start_statef);
1586 file_startstate = fopen(buffer_startstate,
"r");
1588 if (file_startstate == NULL) {
1589 log_msg(NULL, 5, 0,
"Not able to open state file: %s", buffer_startstate);
1590 }
else if (param_globals::output_level) {
1591 log_msg(NULL, 0, 0,
"Open state file for eikonal model: %s", buffer_startstate);
1594 int ListVal, numChangesVal, numChanges2Val;
1595 float T_AVal, T_RVal, TA_oldVal, D_IVal, PCLVal;
1598 while (fscanf(file_startstate,
"%d %d %d %f %f %f %f", &ListVal, &numChangesVal, &numChanges2Val, &T_AVal, &T_RVal, &TA_oldVal, &D_IVal) == 7) {
1599 List[index] = ListVal;
1602 T_A[index] = T_AVal;
1603 T_R[index] = T_RVal;
1604 TA_old[index] = TA_oldVal;
1605 D_I[index] = D_IVal;
1608 if (param_globals::output_level)
log_msg(NULL, 0, 0,
"Number of nodes in active list: %i",
sum(
List));
1610 fclose(file_startstate);
1617 const char* h1 =
" ------ ---------- ---------- ------- ------- | List logic ----- -------- | Neighbor node ---- |";
1618 const char* h2 =
" cycle AT old AT RT DI | Status Entry Exit | ID AT |";
1621 log_msg(NULL, 3, 0,
"%s error: Could not open file %s in %s. Turning off logging.\n",
1631 if (!this->logger)
return;
1638 std::ostringstream oss_TA, oss_TA_, oss_TR, oss_DI, oss_nbnTA;
1639 oss_TA << this->
T_A;
1640 oss_TA_ << this->
T_A_;
1641 oss_TR << this->
T_R;
1642 oss_DI << this->
D_I;
1646 snprintf(cbuf,
sizeof cbuf,
"%7s %10s",
"-",
"-");
1648 snprintf(cbuf,
sizeof cbuf,
"%7lld %10s",
static_cast<long long>(this->
idXNB), oss_nbnTA.str().c_str());
1651 snprintf(abuf,
sizeof abuf,
"%6lld %10s %10s %7s %7s",
1652 static_cast<long long>(this->
cycle),
1653 oss_TA.str().c_str(), oss_TA_.str().c_str(), oss_TR.str().c_str(), oss_DI.str().c_str());
1656 unsigned char flag = cflg ?
ECHO : 0;
1657 log_msg(this->logger, 0, flag |
FLUSH |
NONL,
"%9.3f %s | %s | %s |\n", time, abuf, bbuf, cbuf);
1661 this->T_A_ = this->
T_A;
1662 this->T_A = std::numeric_limits<double>::quiet_NaN();
1663 this->T_R = std::numeric_limits<double>::quiet_NaN();
1664 this->D_I = std::numeric_limits<double>::quiet_NaN();
1666 this->nbn_T_A = std::numeric_limits<double>::quiet_NaN();
1673 const char* stat_str;
1674 const char* reas_str;
1695 template<
int MESH_SIZE>
1700 const double& u1 = values[0];
1702 if constexpr (MESH_SIZE == 2) {
1703 return tsitsiklis_update_line({x1,x2}, D, u1);
1705 }
else if constexpr (MESH_SIZE == 3) {
1707 const double& u2 = values[1];
1708 return tsitsiklis_update_triangle({x1,x2,x3}, D, {u1,u2});
1710 }
else if constexpr (MESH_SIZE == 4) {
1713 const double& u2 = values[1];
1714 const double& u3 = values[2];
1716 double u_tet = tsitsiklis_update_tetra({x1,x2,x3,x4}, D, {u1,u2,u3});
1718 u_tet = std::numeric_limits<double>::infinity();
1721 double u_face1 = tsitsiklis_update_triangle({x1, x2, x4}, D, {u1, u2});
1722 double u_face2 = tsitsiklis_update_triangle({x1, x3, x4}, D, {u1, u3});
1723 double u_face3 = tsitsiklis_update_triangle({x2, x3, x4}, D, {u2, u3});
1725 double u_tri =
std::min({u_face1, u_face2, u_face3});
1730 template <
int MESH_SIZE>
1733 const double& value)
1741 template <
int MESH_SIZE>
1742 double LocalSolver<MESH_SIZE>::tsitsiklis_update_triangle(
const std::array<SF::Point, 3>& base,
1744 const std::array<double, 2>& values)
1749 const double& u1 = values[0];
1750 const double& u2 = values[1];
1751 double result = std::numeric_limits<double>::infinity();
1764 double denominator = p11 - k * k;
1765 double sqrt_val = (p11 * p22 - p12 * p12) / denominator;
1767 if (denominator > 1e-12) {
1768 const double sqrt_val = (p11 * p22 - p12 * p12) / denominator;
1769 if (sqrt_val >= 0.0) {
1770 const double rhs = k * std::sqrt(sqrt_val);
1771 double alpha1 = -(p12 + rhs) / p11;
1772 double alpha2 = -(p12 - rhs) / p11;
1777 for (
double alpha : {alpha1, alpha2}) {
1778 SF::Point x_interp = x1 * alpha + x2 * (1.0 - alpha);
1781 double u3 = alpha * u1 + (1.0 - alpha) * u2 + norm_D;
1788 double u_edge1 = tsitsiklis_update_line({x1,x3}, D, u1);
1789 double u_edge2 = tsitsiklis_update_line({x2,x3}, D, u2);
1791 return std::min({result, u_edge1, u_edge2});
1794 template <
int MESH_SIZE>
1795 double LocalSolver<MESH_SIZE>::tsitsiklis_update_tetra(
const std::array<SF::Point, 4>& base,
1797 const std::array<double, 3>& values)
1804 const double& u1 = values[0];
1805 const double& u2 = values[1];
1806 const double& u3 = values[2];
1813 const double k1 = u1 - u3;
1814 const double k2 = u2 - u3;
1823 const double r21 = r12;
1826 const double r31 = r13;
1827 const double r32 = r23;
1829 const double A1 = k2 * r11 - k1 * r12;
1830 const double A2 = k2 * r21 - k1 * r22;
1831 const double B = k2 * r31 - k1 * r32;
1832 const double k = k1 - (A1 / A2) * k2;
1833 const SF::Point z1 = y1 - (A1 / A2) * y2;
1834 const SF::Point z2 = y3 - (B / A2) * y2;
1842 const double denominator = p11 - k*k;
1843 const double sqrt_val = (p11 * p22 - (p12 * p12)) / denominator;
1844 const double rhs = k * std::sqrt(sqrt_val);
1846 double alpha1 = -(p12 + rhs) / p11;
1847 double alpha2 = -(B + alpha1 * A1) / A2;
1850 const double EPS = 1e-16;
1851 if ((std::abs(A1) < EPS) && (std::abs(A2) < EPS)) {
1852 alpha1 = (r12 * r23 - r13 * r22) / (r11 * r22 - (r12 * r12));
1853 alpha2 = (r12 * r13 - r11 * r23) / (r11 * r22 - (r12 * r12));
1854 }
else if ((std::abs(A1) < EPS) && (std::abs(A2) > EPS)) {
1857 }
else if ((std::abs(A1) > EPS) && (std::abs(A2) < EPS)) {
1862 double alpha3 = 1 - alpha1 - alpha2;
1863 const SF::Point dist = x4 - (alpha1 * x1 + alpha2 * x2 + alpha3 * x3);
1865 if (alpha1 < -EPS || alpha2 < -EPS || alpha3 < -EPS ||
1866 alpha1 > 1.0+EPS || alpha2 > 1.0+EPS || alpha3 > 1.0+EPS) {
1867 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.
T * data()
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)