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 failed to converge for lead %s after %d iterations with residual %g! Reason: %s.", current_lead.c_str(),
168 static_cast<int>(elec.ellip_solver.lin_solver->niter), elec.ellip_solver.lin_solver->final_residual,
169 elec.ellip_solver.lin_solver->reason_str());
173 i2e->
backward(*elec.ellip_solver.phie, *elec.ellip_solver.phie_i);
177 Z[lead_idx]->deep_copy(*elec.ellip_solver.phie_i);
179 if(p2c) (*p2c)(*elec.ellip_solver.phie_i,
true);
180 write_leadfield_igb(current_lead, elec.ellip_solver.phie_i);
182 log_msg(0, 0, 0,
"Leadfield computation complete.");
185 void Leadfield::load(Electrics & elec)
187 collect_lead_names(elec.stimuli);
189 int num_leads = lead_names.size();
191 log_msg(0, 5, 0,
"Error: 'load' called but no leads defined in config!");
197 Z.resize(num_leads,
nullptr);
198 for(
int i = 0; i < num_leads; i++)
202 std::string dir = lf_dir;
203 if(!dir.empty() && dir.back() !=
'/') dir +=
"/";
205 size_t global_nodes = Z[0]->gsize();
210 for(
int i = 0; i < num_leads; i++) {
211 std::string name = lead_names[i];
212 std::string fpath = dir +
"LF_" + name +
".igb";
216 f = fopen(fpath.c_str(),
"r");
218 log_msg(0, 5, 0,
"Error: Cannot open file %s", fpath.c_str());
222 if(igb.type() == 0) {
223 log_msg(0, 5, 0,
"Error: Corrupt or unrecognized IGB header in %s", fpath.c_str());
224 fclose(f); f =
nullptr; err = 1;
225 }
else if((
size_t)igb.x() != global_nodes) {
226 log_msg(0, 5, 0,
"Error: Node count mismatch in %s! Expected %zu, got %d",
227 fpath.c_str(), global_nodes, igb.x());
228 fclose(f); f =
nullptr; err = 1;
234 size_t nread = Z[i]->read_binary<
float>(f);
236 if(nread != Z[i]->gsize()) {
237 log_msg(0, 5, 0,
"Error: Incomplete read from %s! Expected %zu elements, got %zu.",
238 fpath.c_str(), Z[i]->gsize(), nread);
241 if(p2c) (*p2c)(*Z[i],
false);
243 log_msg(0, 0, 0,
" - Loaded %s", fpath.c_str());
249 if(Z.empty())
return;
257 for(
size_t i = 0; i < Z.size(); i++) {
258 double ecg = Z[i]->dot(*Im);
260 trace_data[i].push_back((
float)ecg);
267 log_msg(0, 5, 0,
"Error: Leadfield requires active EP physics! Aborting.");
275 if(lf_dir.length() > 0) {
278 log_msg(0, 0, 0,
"Computing leadfields...");
282 int num_leads = lead_names.size();
284 log_msg(0, 5, 0,
"No leads detected. Exiting.");
289 trace_data.resize(num_leads);
290 for(
auto & vec : trace_data) vec.clear();
298 int total_frames = 0;
311 total_frames = head.
t();
312 file_dt = head.
inc_t();
314 if((
size_t)head.
x() != vm_step->
gsize()) {
315 log_msg(0, 5, 0,
"Error: Vm dimension in %s does not match mesh. Aborting.", vm_file.c_str());
320 stride = (int)((ecg_timedt / file_dt) + 0.5);
321 if(stride < 1) stride = 1;
323 log_msg(0, 1, 0,
" VM file: dt=%.4f ms, target=%.4f ms, stride=%d",
324 file_dt, ecg_timedt, stride);
328 log_msg(0, 5, 0,
"Error: Could not open %s", vm_file.c_str());
335 MPI_Bcast(&total_frames, 1, MPI_INT, 0,
SF_COMM);
336 MPI_Bcast(&file_dt, 1, MPI_FLOAT, 0,
SF_COMM);
337 MPI_Bcast(&stride, 1, MPI_INT, 0,
SF_COMM);
339 if(total_frames == 0) {
344 log_msg(0, 0, 0,
"Computing ECG for %d leads, %d frames...", num_leads, total_frames);
347 for(
int t = 0; t < total_frames; t++) {
349 if(nread != vm_step->
gsize()) {
350 log_msg(0, 5, 0,
"Error: %s read incomplete data slice from %s!", __func__, vm_file.c_str());
354 bool forward =
false;
355 if(sc) (*sc)(*vm_step, forward);
361 if(
get_rank() == 0 && fd) fclose(fd);
366 for(
size_t i = 0; i < lead_names.size(); i++) {
367 std::string fname =
"ECG_" + lead_names[i] +
".dat";
368 FILE* f = fopen(fname.c_str(),
"w");
370 for(
size_t t = 0; t < trace_data[i].size(); t++) {
371 double time = t * file_dt * stride;
372 fprintf(f,
"%g %g\n", time, trace_data[i][t]);
376 log_msg(0, 5, 0,
"Error: Cannot write %s", fname.c_str());
383 log_msg(0, 0, 0,
"ECG post-processing complete.");
#define SF_COMM
the default SlimFem MPI communicator
opencarp::real_t SF_real
Global scalar type.
#define EXIT(A)
finalize the backend, if it runs, and exit
#define CALI_CXX_MARK_FUNCTION
virtual T gsize() const =0
size_t read_binary(FILE *fd)
Redistributes vector data from indexing "a" to indexing "b".
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)
void region_mask(mesh_t meshspec, SF::vector< RegionSpecs > ®spec, SF::vector< int > ®ionIDs, bool mask_elem, const char *reglist, FILE_SPEC logger)
classify elements/points as belonging to a region
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.
SF::meshdata< mesh_int_t, mesh_real_t > sf_mesh
void apply_stim_to_vector(const stimulus &s, sf_vec &vec, bool add)
T get_global(T in, MPI_Op OP, MPI_Comm comm=SF_COMM)
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.
int get_rank(MPI_Comm comm=SF_COMM)
SF::abstract_matrix< SF_int, SF_real > sf_mat
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.