31 #include "petsc_utils.h"
43 log_msg(NULL, 5, 0,
"DREAM/Eikonal physics currently do not support MPI parallelization. Use openMP instead. Aborting!");
53 logger =
f_open(
"eikonal.log", param_globals::experiment != 4 ?
"w" :
"r");
77 param_globals::dt, 0,
"elec::ref_dt",
"TS");
101 log_msg(NULL, 0, 0,
"All done in %f sec.",
float(t2 - t1));
109 m->
regions.resize(param_globals::num_gregions);
112 log_msg(
logger, 0, 0,
"Setting up %s tissue poperties for %d regions ..", grid_name,
113 param_globals::num_gregions);
118 for (
size_t i = 0; i < m->
regions.size(); i++, reg++) {
119 if (!strcmp(param_globals::gregion[i].
name,
"")) {
120 snprintf(buf,
sizeof buf,
", gregion_%d",
int(i));
121 param_globals::gregion[i].name =
dupstr(buf);
124 reg->
regname = strdup(param_globals::gregion[i].
name);
126 reg->
nsubregs = param_globals::gregion[i].num_IDs;
132 for (
int j = 0; j < reg->
nsubregs; j++)
133 reg->
subregtags[j] = param_globals::gregion[i].ID[j];
137 elecMaterial* emat =
new elecMaterial();
140 emat->InVal[0] = param_globals::gregion[i].g_il;
141 emat->InVal[1] = param_globals::gregion[i].g_it;
142 emat->InVal[2] = param_globals::gregion[i].g_in;
144 emat->ExVal[0] = param_globals::gregion[i].g_el;
145 emat->ExVal[1] = param_globals::gregion[i].g_et;
146 emat->ExVal[2] = param_globals::gregion[i].g_en;
148 emat->BathVal[0] = param_globals::gregion[i].g_bath;
149 emat->BathVal[1] = param_globals::gregion[i].g_bath;
150 emat->BathVal[2] = param_globals::gregion[i].g_bath;
153 for (
int j = 0; j < 3; j++) {
154 emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
155 emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
156 emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
165 const char* file = g ==
Eikonal::intra_grid ? param_globals::gi_scale_vec : param_globals::ge_scale_vec;
169 if (num_file_entries != mesh.g_numelem)
170 log_msg(0, 4, 0,
"%s warning: number of %s conductivity scaling entries does not match number of elements!",
176 escale->read_ascii(g ==
Eikonal::intra_grid ? param_globals::gi_scale_vec : param_globals::ge_scale_vec);
190 escale->release_ptr(p);
194 void Eikonal::setup_mappings()
203 log_msg(
logger, 0, 0,
"%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
207 log_msg(
logger, 0, 0,
"%s: Setting up intracellular PETSc to canonical permutation.", __func__);
218 case EIKONAL: solve_EIKONAL();
break;
219 case DREAM: solve_DREAM();
break;
220 default: solve_RE();
break;
251 double curtime =
timing(t2, t1);
282 void Eikonal::setup_stimuli()
286 bool dumpTrace =
true;
290 stimuli.resize(param_globals::num_stim);
291 for (
int i = 0; i < param_globals::num_stim; i++) {
295 if (param_globals::stim[i].crct.type != 0 && param_globals::stim[i].crct.type != 9) {
298 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);
307 log_msg(NULL, 2, 0,
"Only geometry, start time, npls, and bcl of stim[%i] are used", i);
316 void Eikonal::stimulate_intracellular()
326 if (param_globals::operator_splitting) {
336 *ps.tmp_i1 = *ps.IIon;
337 *ps.tmp_i1 -= *ps.Irhs;
342 ps.mass_i->mult(*ps.tmp_i1, *ps.Irhs);
344 *ps.Irhs = *ps.tmp_i1;
352 if (illum_vec == NULL) {
353 log_msg(0, 5, 0,
"Cannot apply illumination stim: global vector not present!");
368 void Eikonal::clamp_Vm()
376 void Eikonal::setup_output()
384 restr_i, param_globals::num_io_nodes > 0);
386 if (param_globals::dataout_i) {
395 if (strcmp(param_globals::dream.
output.idifffile,
"") != 0) {
402 if (strcmp(param_globals::dream.
output.idifffile,
"") != 0) {
410 if (param_globals::num_trace) {
412 open_trace(
ion.
miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
419 void Eikonal::dump_matrices()
421 std::string bsname = param_globals::dump_basename;
427 if (param_globals::parab_solve == 1) {
429 fn = bsname +
"_Ki_CN.bin";
432 fn = bsname +
"_Ki.bin";
435 fn = bsname +
"_Mi.bin";
449 val = std::nan(
"NaN");
463 s_unit =
stimuli[sidx].pulse.wave.f_unit;
468 void Eikonal::setup_solvers()
481 if (param_globals::dump2MatLab) dump_matrices();
485 void Eikonal::checkpointing()
494 snprintf(save_fnm,
sizeof save_fnm,
"%s.%s.roe", param_globals::write_statef, tsav_ext);
503 snprintf(save_fnm,
sizeof save_fnm,
"checkpoint.%.1f.roe", tm.time);
508 void Eikonal::solve_EIKONAL()
518 void Eikonal::solve_RE()
529 void Eikonal::solve_DREAM()
547 void Eikonal::solve_RD()
585 if (param_globals::output_level > 1)
log_msg(0, 0, 0,
"\n *** Initializing Eikonal Solver ***\n");
588 if (param_globals::dream.
output.debugNode >= 0) {
589 nodeData.
idX = param_globals::dream.output.debugNode;
591 snprintf(buf,
sizeof buf,
"node_%d.dat",
nodeData.
idX);
621 switch (mesh.
type[0]) {
635 log_msg(0, 5, 0,
"Error: Type of element is not compatible with this version of the eikonal model. Use tetrahedra or triangles");
640 if (strlen(param_globals::start_statef) > 0) load_state_file();
642 create_node_to_node_graph();
644 translate_stim_to_eikonal();
646 precompute_squared_anisotropy_metric();
666 for (
size_t i = 0; i < rs.
size(); i++) {
667 rs[i].nsubregs = param_globals::imp_region[i].num_IDs;
668 rs[i].subregtags = param_globals::imp_region[i].ID;
669 for (
int j = 0; j < rs[i].nsubregs; j++) {
670 if (rs[i].subregtags[j] == -1 &&
get_rank() == 0)
671 log_msg(NULL, 3,
ECHO,
"Warning: not all %u IDs provided for imp_region[%u]!\n", rs[i].nsubregs, i);
674 if (rs.
size() == 1) {
681 #pragma omp parallel for schedule(dynamic)
682 for (
int v = 0; v < mesh.
l_numpts; v++) {
683 int reg = regionIDs[v];
685 const auto& region_diff = param_globals::imp_region[reg].dream.Idiff;
686 const auto& region_rest = param_globals::imp_region[reg].dream.CVrest;
691 if (model ==
GAUSS) {
692 diff_cur[v].alpha_1 = region_diff.alpha_i[0];
693 diff_cur[v].alpha_2 = region_diff.alpha_i[1];
694 diff_cur[v].alpha_3 = region_diff.alpha_i[2];
695 diff_cur[v].beta_1 = region_diff.beta_i[0];
696 diff_cur[v].beta_2 = region_diff.beta_i[1];
697 diff_cur[v].beta_3 = region_diff.beta_i[2];
698 diff_cur[v].gamma_1 = region_diff.gamma_i[0];
699 diff_cur[v].gamma_2 = region_diff.gamma_i[1];
700 diff_cur[v].gamma_3 = region_diff.gamma_i[2];
703 diff_cur[v].tau_F = region_diff.tau_F;
704 diff_cur[v].V_th = region_diff.V_th;
710 denom_cvrest[v] = log(region_rest.rho) / region_rest.psi;
712 if (param_globals::output_level)
log_msg(NULL, 0, 0,
"Diffusion current and CV restitution initialized in %f sec.",
timing(t2, t1));
715 void eikonal_solver::translate_stim_to_eikonal()
722 std::vector<std::pair<SF_real, int>> stim_events;
723 stim_events.reserve(param_globals::num_stim * 8);
725 for (
int stim_idx = 0; stim_idx < stimuliRef->size(); ++stim_idx) {
726 const stimulus& s = (*stimuliRef)[stim_idx];
727 for (
int idx_pls = 0; idx_pls < s.
ptcl.
npls; ++idx_pls) {
729 stim_events.emplace_back(start_time, stim_idx);
734 std::sort(stim_events.begin(), stim_events.end(),
735 [](
auto& a,
auto& b) { return a.first < b.first; });
738 size_t total_nodes = 0;
739 for (
auto& ev : stim_events)
740 total_nodes += (*stimuliRef)[ev.second].electrode.vertices.size();
746 for (
auto& ev : stim_events) {
747 const stimulus& s = (*stimuliRef)[ev.second];
755 if (param_globals::output_level)
756 log_msg(NULL, 0, 0,
"Translating stimuli for eikonal model done in %f sec.",
timing(t2, t1));
759 void eikonal_solver::create_node_to_node_graph()
768 std::vector<std::vector<mesh_int_t>> thread_neighbors(
num_pts);
773 std::vector<char> mark(
num_pts, 0);
775 #pragma omp for schedule(dynamic)
776 for (
int point_idx = 0; point_idx <
num_pts; point_idx++) {
778 std::vector<mesh_int_t> neighbors;
782 for (
int eedsp = 0; eedsp < numNBElem; eedsp++) {
786 for (
int nndsp = 0; nndsp <
MESH_SIZE; nndsp++) {
789 if (nb == point_idx)
continue;
798 for (
int nb : neighbors) mark[nb] = 0;
801 thread_neighbors[point_idx] = std::move(neighbors);
806 for (
int i = 0; i <
num_pts; i++) {
813 #pragma omp parallel for schedule(static)
814 for (
int i = 0; i <
num_pts; i++) {
815 std::copy(thread_neighbors[i].begin(),
816 thread_neighbors[i].end(),
820 if (param_globals::output_level) {
821 log_msg(NULL, 0, 0,
"Node-to-node graph done in %f sec.",
timing(t2, t1));
825 void eikonal_solver::precompute_squared_anisotropy_metric()
832 S.resize(mesh.l_numelem);
833 CV_L.resize(mesh.l_numelem);
847 #pragma omp for schedule(static)
848 for (
int eidx = 0; eidx < mesh.l_numelem; eidx++) {
849 double vl = 0.0, vt = 0.0, vn = 0.0;
852 for (
int g = 0; g < param_globals::num_gregions; g++) {
853 const auto& reg = param_globals::gregion[g];
854 const auto& dream = reg.dream;
855 for (
int j = 0; j < reg.num_IDs; j++) {
856 if (mesh.tag[eidx] == reg.ID[j]) {
870 double AR_T2 = (vl / vt) * (vl / vt);
871 double AR_N2 = (vl / vn) * (vl / vn);
873 f.
x = mesh.fib[3 * eidx + 0];
874 f.
y = mesh.fib[3 * eidx + 1];
875 f.
z = mesh.fib[3 * eidx + 2];
878 s.
x = mesh.she[3 * eidx + 0];
879 s.
y = mesh.she[3 * eidx + 1];
880 s.
z = mesh.she[3 * eidx + 2];
898 if (param_globals::output_level)
899 log_msg(NULL, 0, 0,
"Anisotropy tensors precomputed in %f sec.",
timing(t2, t1));
911 std::vector<mesh_int_t> activeList;
912 activeList.reserve(
num_pts / 10);
913 std::vector<char> in_active(
num_pts, 0);
923 add_to_active(activeList, in_active, nb);
930 while (!activeList.empty() && niter <= param_globals::dream.fim.max_iter) {
931 const std::vector<mesh_int_t> activeVec = std::move(activeList);
936 std::vector<mesh_int_t> local_active;
937 local_active.reserve(64);
939 #pragma omp for schedule(dynamic)
940 for (
size_t i = 0; i < activeVec.size(); ++i) {
942 remove_from_active(in_active,
id);
947 #pragma omp atomic write
950 if (std::fabs(p - q) < param_globals::dream.fim.tol) {
956 #pragma omp atomic write
962 local_active.push_back(
id);
970 add_to_active(activeList, in_active, nb);
979 auto [minIt, maxIt] = std::minmax_element(
T_A.
begin(),
T_A.
end());
984 double* atc =
AT->
ptr();
986 #pragma omp parallel for simd
988 atc[i] = (t_a[i] ==
inf ? -1.0 : t_a[i]);
992 auto dur =
timing(t1, t0);
1011 double time2stop_eikonal = param_globals::dream.tau_inc;
1012 float maxadvance =
user_globals::tm_manager->
time + param_globals::dream.tau_s + param_globals::dream.tau_inc + param_globals::dream.tau_max;
1023 if (
List[indX] == 0)
continue;
1025 q = compute_coherence(indX);
1030 if (q > maxadvance)
continue;
1032 if (abs(p - q) < param_globals::dream.fim.tol || (
num_changes[indX] > param_globals::dream.fim.max_iter) || (q ==
inf || p ==
inf)) {
1036 if (
List[indXNB] == 1) {
1043 qNB = compute_coherence(indXNB);
1049 bool ignore_L2_if_valid = node_is_valid && qNB > pNB &&
nReadded2List[indXNB] >= param_globals::dream.fim.max_addpt;
1051 if (node_is_valid && (
nReadded2List[indXNB] < param_globals::dream.fim.max_addpt) || ignore_L2_if_valid) {
1059 if (param_globals::dream.
output.debugNode == indXNB) {
1068 if (param_globals::dream.
output.debugNode == indX) {
1077 update_Ta_in_active_list();
1079 if (
actMIN > actMIN_old) {
1080 progress_time =
actMIN - actMIN_old;
1085 time2stop_eikonal -= progress_time;
1088 }
while (time2stop_eikonal > 0 &&
sum(
List) > 0);
1095 double* atc =
AT->
ptr();
1096 double* rpt =
RT->
ptr();
1110 auto dur =
timing(t1, t0);
1119 if (param_globals::dream.
output.debugNode >= 0) {
1130 case 4:
return update_impl<4>(indX, CVrest_factor, isDREAM);
1131 case 3:
return update_impl<3>(indX, CVrest_factor, isDREAM);
1132 case 2:
return update_impl<2>(indX, CVrest_factor, isDREAM);
1149 std::array<SF::Point, N> base;
1150 std::array<double, N> values;
1151 std::array<int, N> nodeIDs;
1154 for (std::size_t j = 0; j < N; j++) {
1155 int n_i =
e2n_con[indEle + j];
1157 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];
1158 values[k] =
T_A[n_i];
1162 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];
1163 values[N - 1] =
T_A[indX];
1164 nodeIDs[N - 1] = indX;
1168 double cv =
CV_L[Elem_i] * CVrest_factor;
1169 if (cv == 0.0)
continue;
1173 if (!CheckValidity || !is_not_valid_update<N>(nodeIDs, time)) {
1174 LocalSolver<N> solver(D, base, values);
1176 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1185 if constexpr (N - 1 == 3) {
1187 for (
int i = 0; i < (N - 1); ++i) {
1188 for (
int j = i + 1; j < (N - 1); ++j) {
1190 std::array<int, 3> tri_ids{nodeIDs[i], nodeIDs[j], nodeIDs[N - 1]};
1191 if (is_not_valid_update<3>(tri_ids, time))
continue;
1193 std::array<SF::Point, 3> tri_pts{base[i], base[j], base[N - 1]};
1194 std::array<double, 3> tri_vals{values[i], values[j], values[N - 1]};
1196 LocalSolver<3> solver(D, tri_pts, tri_vals);
1199 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1207 for (
int i = 0; i < N - 1; i++) {
1208 std::array<int, 2> edge_ids{nodeIDs[i], nodeIDs[N - 1]};
1209 if (is_not_valid_update<2>(edge_ids, time))
continue;
1211 std::array<SF::Point, 2> edge_pts{base[i], base[N - 1]};
1212 std::array<double, 2> edge_vals{values[i], values[N - 1]};
1214 LocalSolver<2> solver(D, edge_pts, edge_vals);
1216 if (
min > tmp && tmp >
T_R[indX] && compute_H(indX, tmp) > 0.0) {
1226 bool eikonal_solver::is_not_valid_update(
const std::array<int, N>& nodeIDs,
double time)
1228 const int target = nodeIDs[N - 1];
1229 const bool failedStim = (
stim_status[target] == 2);
1231 for (
int i = 0; i < N - 1; i++) {
1232 const int nb = nodeIDs[i];
1234 if (
T_A[nb] < time)
return true;
1235 if (
T_A[nb] <
T_R[nb])
return true;
1236 if (failedStim &&
stim_status[nb] == 1)
return true;
1245 const double delay = (
stim_status[indX] == 2) ? 5.0 : 0.0;
1249 (
T_R[indX] + delay) == -400.0 ||
1250 std::fabs(tmpTA -
TA_old[indX]) < param_globals::dream.fim.tol ||
1251 T_R[indX] > tmpTA) {
1256 const double DI = tmpTA - (
T_R[indX] + delay);
1261 const double Factor_DI = 1.0 -
rho_cvrest[indX] * std::exp(exponent);
1263 if (Factor_DI <= 0.0) {
1268 return (DI <=
theta_cvrest[indX]) ? -Factor_DI : Factor_DI;
1275 SF_real q = update(indX, scaling,
true);
1277 for (
int niter = 0; niter < param_globals::dream.fim.max_coh; ++niter) {
1278 scaling = compute_H(indX, p);
1279 q = update(indX, std::fabs(scaling),
true);
1281 if (std::fabs(p - q) < param_globals::dream.fim.tol)
1288 return (compute_H(indX, q) < 0.0) ?
T_A[indX] : q;
1291 void eikonal_solver::compute_bc()
1293 bool EmpList =
sum(
List) == 0;
1305 bool cond2add = add_node_neighbor_to_list(
T_R[indNode],
T_A[indNode], TimeSt);
1306 if (cond2add && compute_H(indNode, TimeSt) > 0) {
1307 T_A[indNode] = TimeSt;
1311 if (param_globals::dream.
output.debugNode == indNode) {
1316 for (
int ii =
n2n_dsp[indNode]; ii <
n2n_dsp[indNode + 1]; ii++) {
1318 if (
List[indXNB] == 0) {
1320 if (param_globals::dream.
output.debugNode == indXNB) {
1329 if (cond2add && compute_H(indNode, TimeSt) <= 0) {
1349 if (indNode == -1)
continue;
1352 bool cond2add = add_node_neighbor_to_list(
T_R[indNode],
T_A[indNode], TimeSt);
1353 if (cond2add && compute_H(indNode, TimeSt) > 0) {
1354 T_A[indNode] = TimeSt;
1358 if (param_globals::dream.
output.debugNode == indNode) {
1363 for (
int ii =
n2n_dsp[indNode]; ii <
n2n_dsp[indNode + 1]; ii++) {
1365 if (
List[indXNB] == 0) {
1367 if (param_globals::dream.
output.debugNode == indXNB) {
1383 if (cond2add && compute_H(indNode, TimeSt) <= 0) {
1399 bool act_is_in_safety_window, empty_list_and_illegal_stimulus, empty_stimulus, stimulus_is_in_safety_window;
1401 act_is_in_safety_window = (
actMIN > param_globals::dream.tau_s) && (
actMIN - time > param_globals::dream.tau_s);
1404 empty_list_and_illegal_stimulus = (
sum(
List) == 0) && (empty_stimulus || stimulus_is_in_safety_window);
1406 return act_is_in_safety_window || empty_list_and_illegal_stimulus;
1409 void eikonal_solver::update_Ta_in_active_list()
1411 bool First_in_List = 1;
1413 for (
size_t j = 0; j <
List.size(); j++) {
1414 if (
T_A[j] < 0)
continue;
1415 if (
List[j] == 1 && First_in_List) {
1430 for (
size_t j = 0; j <
List.size(); j++) {
1445 return ((
RT < newTA) && (newTA < oldTA)) ||
1446 ((oldTA <
RT) && (
RT < newTA));
1454 const double thresh = param_globals::dream.repol_time_thresh;
1456 for (
int ind_nodes = 0; ind_nodes <
num_pts; ind_nodes++) {
1457 if (old_ptr[ind_nodes] >= thresh && new_ptr[ind_nodes] < thresh) {
1458 T_R[ind_nodes] = time;
1472 int max_threads = omp_get_max_threads();
1473 omp_set_num_threads(1);
1481 for (
int i = 0; i < miif->
N_IIF; i++) {
1482 if (!miif->
N_Nodes[i])
continue;
1491 int ind_gb = (miif->
NodeLists[i][current]);
1494 double Vm_old = miif->
ldata[i][limpet::Vm][current];
1495 double Iion_old = miif->
ldata[i][limpet::Iion][current];
1496 double prev_R =
T_R[ind_gb];
1498 bool cond_2upd =
T_R[ind_gb] <=
T_A[ind_gb] &&
T_A[ind_gb] !=
inf && Vm_old > param_globals::dream.repol_time_thresh;
1501 double elapsed_time = 0;
1502 double prev_Vm = miif->
ldata[i][limpet::Vm][current];
1507 miif->
ldata[i][limpet::Vm][current] -= miif->
ldata[i][limpet::Iion][current] * param_globals::dt;
1508 if (miif->
ldata[i][limpet::Vm][current] < param_globals::dream.repol_time_thresh) {
1509 T_R[ind_gb] = time + elapsed_time;
1512 }
while (elapsed_time < 600);
1515 miif->
ldata[i][limpet::Vm][current] = Vm_old;
1516 miif->
ldata[i][limpet::Iion][current] = Iion_old;
1519 }
while (current < miif->N_Nodes[i]);
1524 omp_set_num_threads(max_threads);
1528 auto dur =
timing(t1, t0);
1544 for (
size_t j = 0; j < alg_nod.
size(); j++) {
1548 double TA =
T_A[loc_nodal_idx];
1549 if (!(time >= TA && (TA + 5) >= time &&
List[loc_nodal_idx] == 0))
1552 double dT = time - TA;
1553 auto& node =
diff_cur[loc_nodal_idx];
1555 switch (node.model) {
1557 double term1 = (dT - node.beta_1) / node.gamma_1;
1558 double term2 = (dT - node.beta_2) / node.gamma_2;
1559 double term3 = (dT - node.beta_3) / node.gamma_3;
1560 c[loc_petsc_idx] = node.alpha_1 * exp(-term1 * term1) + node.alpha_2 * exp(-term2 * term2) + node.alpha_3 * exp(-term3 * term3);
1564 double e_on = (dT >= 0.0) ? 1.0 : 0.0;
1565 double e_off = (v[loc_petsc_idx] < node.V_th) ? 1.0 : 0.0;
1566 c[loc_petsc_idx] = node.A_F / node.tau_F * exp(dT / node.tau_F) * e_on * e_off;
1581 FILE* file_writestate;
1582 char buffer_writestate[1024];
1583 snprintf(buffer_writestate,
sizeof buffer_writestate,
"%s.%s.roe.dat", param_globals::write_statef, tsav_ext);
1584 file_writestate = fopen(buffer_writestate,
"w");
1585 for (
size_t jjj = 0; jjj <
List.size(); jjj++) {
1589 fclose(file_writestate);
1593 void eikonal_solver::load_state_file()
1596 FILE* file_startstate;
1597 char buffer_startstate[strlen(param_globals::start_statef) + 10];
1599 snprintf(buffer_startstate,
sizeof buffer_startstate,
"%s.dat", param_globals::start_statef);
1600 file_startstate = fopen(buffer_startstate,
"r");
1602 if (file_startstate == NULL) {
1603 log_msg(NULL, 5, 0,
"Not able to open state file: %s", buffer_startstate);
1604 }
else if (param_globals::output_level) {
1605 log_msg(NULL, 0, 0,
"Open state file for eikonal model: %s", buffer_startstate);
1608 int ListVal, numChangesVal, numChanges2Val;
1609 float T_AVal, T_RVal, TA_oldVal, D_IVal, PCLVal;
1612 while (fscanf(file_startstate,
"%d %d %d %f %f %f %f", &ListVal, &numChangesVal, &numChanges2Val, &T_AVal, &T_RVal, &TA_oldVal, &D_IVal) == 7) {
1613 List[index] = ListVal;
1616 T_A[index] = T_AVal;
1617 T_R[index] = T_RVal;
1618 TA_old[index] = TA_oldVal;
1619 D_I[index] = D_IVal;
1622 if (param_globals::output_level)
log_msg(NULL, 0, 0,
"Number of nodes in active list: %i",
sum(
List));
1624 fclose(file_startstate);
1631 const char* h1 =
" ------ ---------- ---------- ------- ------- | List logic ----- -------- | Neighbor node ---- |";
1632 const char* h2 =
" cycle AT old AT RT DI | Status Entry Exit | ID AT |";
1635 log_msg(NULL, 3, 0,
"%s error: Could not open file %s in %s. Turning off logging.\n",
1645 if (!this->logger)
return;
1652 std::ostringstream oss_TA, oss_TA_, oss_TR, oss_DI, oss_nbnTA;
1653 oss_TA << this->
T_A;
1654 oss_TA_ << this->
T_A_;
1655 oss_TR << this->
T_R;
1656 oss_DI << this->
D_I;
1660 snprintf(cbuf,
sizeof cbuf,
"%7s %10s",
"-",
"-");
1662 snprintf(cbuf,
sizeof cbuf,
"%7d %10s", this->
idXNB, oss_nbnTA.str().c_str());
1665 snprintf(abuf,
sizeof abuf,
"%6d %10s %10s %7s %7s", this->
cycle, oss_TA.str().c_str(), oss_TA_.str().c_str(), oss_TR.str().c_str(), oss_DI.str().c_str());
1668 unsigned char flag = cflg ?
ECHO : 0;
1669 log_msg(this->logger, 0, flag |
FLUSH |
NONL,
"%9.3f %s | %s | %s |\n", time, abuf, bbuf, cbuf);
1673 this->T_A_ = this->
T_A;
1674 this->T_A = std::numeric_limits<double>::quiet_NaN();
1675 this->T_R = std::numeric_limits<double>::quiet_NaN();
1676 this->D_I = std::numeric_limits<double>::quiet_NaN();
1678 this->nbn_T_A = std::numeric_limits<double>::quiet_NaN();
1685 const char* stat_str;
1686 const char* reas_str;
1707 template<
int MESH_SIZE>
1714 const double& u1 = values[0];
1715 const double& u2 = values[1];
1716 const double& u3 = values[2];
1718 if constexpr (MESH_SIZE == 2) {
1719 return tsitsiklis_update_line({x1,x2}, D, u1);
1721 }
else if constexpr (MESH_SIZE == 3) {
1722 return tsitsiklis_update_triangle({x1,x2,x3}, D, {u1,u2});
1724 }
else if constexpr (MESH_SIZE == 4) {
1725 double u_tet = tsitsiklis_update_tetra({x1,x2,x3,x4}, D, {u1,u2,u3});
1727 u_tet = std::numeric_limits<double>::infinity();
1730 double u_face1 = tsitsiklis_update_triangle({x1, x2, x4}, D, {u1, u2});
1731 double u_face2 = tsitsiklis_update_triangle({x1, x3, x4}, D, {u1, u3});
1732 double u_face3 = tsitsiklis_update_triangle({x2, x3, x4}, D, {u2, u3});
1734 double u_tri =
std::min({u_face1, u_face2, u_face3});
1739 template <
int MESH_SIZE>
1742 const double& value)
1750 template <
int MESH_SIZE>
1751 double LocalSolver<MESH_SIZE>::tsitsiklis_update_triangle(
const std::array<SF::Point, 3>& base,
1753 const std::array<double, 2>& values)
1758 const double& u1 = values[0];
1759 const double& u2 = values[1];
1760 double result = std::numeric_limits<double>::infinity();
1773 double denominator = p11 - k * k;
1774 double sqrt_val = (p11 * p22 - p12 * p12) / denominator;
1776 if (denominator > 1e-12) {
1777 const double sqrt_val = (p11 * p22 - p12 * p12) / denominator;
1778 if (sqrt_val >= 0.0) {
1779 const double rhs = k * std::sqrt(sqrt_val);
1780 double alpha1 = -(p12 + rhs) / p11;
1781 double alpha2 = -(p12 - rhs) / p11;
1786 for (
double alpha : {alpha1, alpha2}) {
1787 SF::Point x_interp = x1 * alpha + x2 * (1.0 - alpha);
1790 double u3 = alpha * u1 + (1.0 - alpha) * u2 + norm_D;
1797 double u_edge1 = tsitsiklis_update_line({x1,x3}, D, u1);
1798 double u_edge2 = tsitsiklis_update_line({x2,x3}, D, u2);
1800 return std::min({result, u_edge1, u_edge2});
1803 template <
int MESH_SIZE>
1804 double LocalSolver<MESH_SIZE>::tsitsiklis_update_tetra(
const std::array<SF::Point, 4>& base,
1806 const std::array<double, 3>& values)
1813 const double& u1 = values[0];
1814 const double& u2 = values[1];
1815 const double& u3 = values[2];
1822 const double k1 = u1 - u3;
1823 const double k2 = u2 - u3;
1832 const double r21 = r12;
1835 const double r31 = r13;
1836 const double r32 = r23;
1838 const double A1 = k2 * r11 - k1 * r12;
1839 const double A2 = k2 * r21 - k1 * r22;
1840 const double B = k2 * r31 - k1 * r32;
1841 const double k = k1 - (A1 / A2) * k2;
1842 const SF::Point z1 = y1 - (A1 / A2) * y2;
1843 const SF::Point z2 = y3 - (B / A2) * y2;
1851 const double denominator = p11 - k*k;
1852 const double sqrt_val = (p11 * p22 - (p12 * p12)) / denominator;
1853 const double rhs = k * std::sqrt(sqrt_val);
1855 double alpha1 = -(p12 + rhs) / p11;
1856 double alpha2 = -(B + alpha1 * A1) / A2;
1859 const double EPS = 1e-16;
1860 if ((std::abs(A1) < EPS) && (std::abs(A2) < EPS)) {
1861 alpha1 = (r12 * r23 - r13 * r22) / (r11 * r22 - (r12 * r12));
1862 alpha2 = (r12 * r13 - r11 * r23) / (r11 * r22 - (r12 * r12));
1863 }
else if ((std::abs(A1) < EPS) && (std::abs(A2) > EPS)) {
1866 }
else if ((std::abs(A1) > EPS) && (std::abs(A2) < EPS)) {
1871 double alpha3 = 1 - alpha1 - alpha2;
1872 const SF::Point dist = x4 - (alpha1 * x1 + alpha2 * x2 + alpha3 * x3);
1874 if (alpha1 < -EPS || alpha2 < -EPS || alpha3 < -EPS ||
1875 alpha1 > 1.0+EPS || alpha2 > 1.0+EPS || alpha3 > 1.0+EPS) {
1876 return std::numeric_limits<double>::infinity();
double SF_real
Use the general double as real 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
Container for a PETSc VecScatter.
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.
Target get_target() const
int get_num_node() const
Gets the number of nodes handled by this IMP.
void compute(int start, int end, GlobalData_t **data)
Perform ionic model computation for 1 time step.
virtual IonIfBase * make_ion_if(Target target, int 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
int * N_Nodes
#nodes for each IMP
int ** 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_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.
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)
size_t root_count_ascii_lines(std::string file, MPI_Comm comm)
count the lines in a ascii file
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.
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.
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)
const char * get_mesh_type_name(mesh_t t)
get a char* to the name of a mesh type
void region_mask(mesh_t meshspec, SF::vector< RegionSpecs > ®spec, SF::vector< int > ®ionIDs, bool mask_elem, const char *reglist)
classify elements/points as belonging to a region
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 setup_dataout(const int dataout, std::string dataout_vtx, mesh_t grid, SF::vector< mesh_int_t > *&restr, bool async)
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)
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.
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
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
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)