12 #include <unordered_set>
19 #include "caliper/cali.h"
28 if(param_globals::lf_vmfile && std::string(param_globals::lf_vmfile).length() > 0)
29 vm_file = param_globals::lf_vmfile;
31 vm_file = param_globals::vofile;
33 ecg_timedt = param_globals::ecg_timedt;
35 if(param_globals::lf_dir)
36 lf_dir = param_globals::lf_dir;
41 for(
sf_vec* z : Z)
delete z;
48 std::unordered_set<std::string> seen;
50 if(s.phys.type ==
LF_I && seen.insert(s.name).second)
51 lead_names.push_back(s.name);
54 void Leadfield::write_leadfield_igb(
const std::string & lead_name,
sf_vec * phie_i)
58 std::string
filename =
"LF_" + lead_name +
".igb";
60 FILE* f_out =
nullptr;
63 f_out = fopen(
filename.c_str(),
"w");
69 head.
x(phie_i->gsize());
83 phie_i->write_binary<
float>(f_out);
87 log_msg(0, 0, 0,
" - Wrote %s (nodes=%zu)",
filename.c_str(), phie_i->gsize());
91 void Leadfield::apply_unit_curr_lead(
const std::string & lead, Electrics & elec)
93 elec.ellip_solver.phiesrc->set(0.0);
95 bool stim_found =
false;
96 for(
const stimulus & s : elec.stimuli) {
97 if(s.name == lead && s.phys.type ==
LF_I) {
105 scaled.pulse.strength *= 1.e12 / vol;
112 elec.ellip_solver.mass_e->mult(*elec.ellip_solver.phiesrc, *elec.ellip_solver.currtmp);
113 elec.ellip_solver.phiesrc->deep_copy(*elec.ellip_solver.currtmp);
117 void Leadfield::construct(Electrics & elec)
121 if(elec.ellip_solver.phie_mat ==
nullptr) {
122 elec.ellip_solver.init();
132 elec.ellip_solver.rebuild_stiffness(elec.mtype, elec.stimuli, elec.logger);
133 elec.ellip_solver.rebuild_mass(elec.logger);
136 elec.ellip_solver.phie->set(0.0);
137 elec.ellip_solver.phie_i->set(0.0);
139 collect_lead_names(elec.stimuli);
141 int num_leads = lead_names.size();
143 log_msg(0, 4, 0,
"Warning: No Leadfield stimuli found. Skipping.");
149 Z.resize(num_leads,
nullptr);
150 for(
int i = 0; i < num_leads; i++)
157 log_msg(0, 0, 0,
"Computing %d leadfield matrices...", num_leads);
159 for(
int lead_idx = 0; lead_idx < num_leads; lead_idx++) {
160 std::string current_lead = lead_names[lead_idx];
161 log_msg(0, 1, 0,
" - Solving lead %d/%d: %s", lead_idx + 1, num_leads, current_lead.c_str());
163 apply_unit_curr_lead(current_lead, elec);
164 (*elec.ellip_solver.lin_solver)(*elec.ellip_solver.phie, *elec.ellip_solver.phiesrc);
166 if(elec.ellip_solver.lin_solver->reason < 0) {
167 log_msg(0, 5, 0,
"Error: Leadfield solver diverged for lead %s!", current_lead.c_str());
171 i2e->
backward(*elec.ellip_solver.phie, *elec.ellip_solver.phie_i);
175 Z[lead_idx]->deep_copy(*elec.ellip_solver.phie_i);
177 if(p2c) (*p2c)(*elec.ellip_solver.phie_i,
true);
178 write_leadfield_igb(current_lead, elec.ellip_solver.phie_i);
180 log_msg(0, 0, 0,
"Leadfield computation complete.");
183 void Leadfield::load(Electrics & elec)
185 collect_lead_names(elec.stimuli);
187 int num_leads = lead_names.size();
189 log_msg(0, 5, 0,
"Error: 'load' called but no leads defined in config!");
195 Z.resize(num_leads,
nullptr);
196 for(
int i = 0; i < num_leads; i++)
200 std::string dir = lf_dir;
201 if(!dir.empty() && dir.back() !=
'/') dir +=
"/";
203 size_t global_nodes = Z[0]->gsize();
208 for(
int i = 0; i < num_leads; i++) {
209 std::string name = lead_names[i];
210 std::string fpath = dir +
"LF_" + name +
".igb";
214 f = fopen(fpath.c_str(),
"r");
216 log_msg(0, 5, 0,
"Error: Cannot open file %s", fpath.c_str());
220 if(igb.type() == 0) {
221 log_msg(0, 5, 0,
"Error: Corrupt or unrecognized IGB header in %s", fpath.c_str());
222 fclose(f); f =
nullptr; err = 1;
223 }
else if((
size_t)igb.x() != global_nodes) {
224 log_msg(0, 5, 0,
"Error: Node count mismatch in %s! Expected %zu, got %d",
225 fpath.c_str(), global_nodes, igb.x());
226 fclose(f); f =
nullptr; err = 1;
232 size_t nread = Z[i]->read_binary<
float>(f);
234 if(nread != Z[i]->gsize()) {
235 log_msg(0, 5, 0,
"Error: Incomplete read from %s! Expected %zu elements, got %zu.",
236 fpath.c_str(), Z[i]->gsize(), nread);
239 if(p2c) (*p2c)(*Z[i],
false);
241 log_msg(0, 0, 0,
" - Loaded %s", fpath.c_str());
247 if(Z.empty())
return;
255 for(
size_t i = 0; i < Z.size(); i++) {
256 double ecg = Z[i]->dot(*Im);
258 trace_data[i].push_back((
float)ecg);
265 log_msg(0, 5, 0,
"Error: Leadfield requires active EP physics! Aborting.");
271 MPI_Barrier(PETSC_COMM_WORLD);
273 if(lf_dir.length() > 0) {
276 log_msg(0, 0, 0,
"Computing leadfields...");
280 int num_leads = lead_names.size();
282 log_msg(0, 5, 0,
"No leads detected. Exiting.");
287 trace_data.resize(num_leads);
288 for(
auto & vec : trace_data) vec.clear();
296 int total_frames = 0;
309 total_frames = head.
t();
310 file_dt = head.
inc_t();
312 if((
size_t)head.
x() != vm_step->
gsize()) {
313 log_msg(0, 5, 0,
"Error: Vm dimension in %s does not match mesh. Aborting.", vm_file.c_str());
318 stride = (int)((ecg_timedt / file_dt) + 0.5);
319 if(stride < 1) stride = 1;
321 log_msg(0, 1, 0,
" VM file: dt=%.4f ms, target=%.4f ms, stride=%d",
322 file_dt, ecg_timedt, stride);
326 log_msg(0, 5, 0,
"Error: Could not open %s", vm_file.c_str());
333 MPI_Bcast(&total_frames, 1, MPI_INT, 0, PETSC_COMM_WORLD);
334 MPI_Bcast(&file_dt, 1, MPI_FLOAT, 0, PETSC_COMM_WORLD);
335 MPI_Bcast(&stride, 1, MPI_INT, 0, PETSC_COMM_WORLD);
337 if(total_frames == 0) {
342 log_msg(0, 0, 0,
"Computing ECG for %d leads, %d frames...", num_leads, total_frames);
345 for(
int t = 0; t < total_frames; t++) {
347 if(nread != vm_step->
gsize()) {
348 log_msg(0, 5, 0,
"Error: %s read incomplete data slice from %s!", __func__, vm_file.c_str());
352 bool forward =
false;
353 if(sc) (*sc)(*vm_step, forward);
359 if(
get_rank() == 0 && fd) fclose(fd);
364 for(
size_t i = 0; i < lead_names.size(); i++) {
365 std::string fname =
"ECG_" + lead_names[i] +
".dat";
366 FILE* f = fopen(fname.c_str(),
"w");
368 for(
size_t t = 0; t < trace_data[i].size(); t++) {
369 double time = t * file_dt * stride;
370 fprintf(f,
"%g %g\n", time, trace_data[i][t]);
374 log_msg(0, 5, 0,
"Error: Cannot write %s", fname.c_str());
381 log_msg(0, 0, 0,
"ECG post-processing complete.");
opencarp::real_t SF_real
Global scalar type.
#define CALI_CXX_MARK_FUNCTION
virtual T gsize() const =0
size_t read_binary(FILE *fd)
Container for a PETSc VecScatter.
void backward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Backward scattering.
A vector storing arbitrary data.
parabolic_solver parab_solver
Solver for the parabolic bidomain equation.
igb_output_manager output_manager
class handling the igb output
int run(Electrics &elec)
Full pipeline: construct or load Z, stream vm.igb, write ECG_*.dat.
void close_files_and_cleanup()
close file descriptors
sf_mat * rhs_parab
rhs matrix to solve parabolic
Tissue level electrics, main Electrics physics class.
Lead field ECG computation.
void init_vector(SF::abstract_vector< T, S > **vec)
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_real get_volume_from_nodes(sf_mat &mass, SF::vector< mesh_int_t > &local_idx)
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
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)
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
T get_global(T in, MPI_Op OP, MPI_Comm comm=PETSC_COMM_WORLD)
Do a global reduction on a variable.
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
void set_elec_tissue_properties(MaterialType *mtype, Electrics::grid_t g, FILE_SPEC logger)
Fill the RegionSpec of an electrics grid with the associated inputs from the param parameters.
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
SF::abstract_vector< SF_int, SF_real > sf_vec
Basic_physic * get_physics(physic_t p, bool error_if_missing)
Convinience function to get a physics.
SF::abstract_matrix< SF_int, SF_real > sf_mat
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.