19 #include <unordered_set>
26 #include "caliper/cali.h"
35 if(param_globals::lf_vmfile && std::string(param_globals::lf_vmfile).length() > 0)
36 vm_file = param_globals::lf_vmfile;
38 vm_file = param_globals::vofile;
40 ecg_timedt = param_globals::ecg_timedt;
42 if(param_globals::lf_dir)
43 lf_dir = param_globals::lf_dir;
48 for(
sf_vec* z : Z)
delete z;
55 std::unordered_set<std::string> seen;
57 if(s.phys.type ==
LF_I && seen.insert(s.name).second)
58 lead_names.push_back(s.name);
61 void Leadfield::write_leadfield_igb(
const std::string & lead_name,
sf_vec * phie_i)
65 std::string
filename =
"LF_" + lead_name +
".igb";
67 FILE* f_out =
nullptr;
70 f_out = fopen(
filename.c_str(),
"w");
76 head.
x(phie_i->gsize());
90 phie_i->write_binary<
float>(f_out);
94 log_msg(0, 0, 0,
" - Wrote %s (nodes=%zu)",
filename.c_str(), phie_i->gsize());
98 void Leadfield::apply_unit_curr_lead(
const std::string & lead, Electrics & elec)
100 elec.ellip_solver.phiesrc->set(0.0);
102 bool stim_found =
false;
103 for(
const stimulus & s : elec.stimuli) {
104 if(s.name == lead && s.phys.type ==
LF_I) {
112 scaled.pulse.strength *= 1.e12 / vol;
119 elec.ellip_solver.mass_e->mult(*elec.ellip_solver.phiesrc, *elec.ellip_solver.currtmp);
120 elec.ellip_solver.phiesrc->deep_copy(*elec.ellip_solver.currtmp);
124 void Leadfield::construct(Electrics & elec)
128 if(elec.ellip_solver.phie_mat ==
nullptr) {
129 elec.ellip_solver.init();
139 elec.ellip_solver.rebuild_stiffness(elec.mtype, elec.stimuli, elec.logger);
140 elec.ellip_solver.rebuild_mass(elec.logger);
143 elec.ellip_solver.phie->set(0.0);
144 elec.ellip_solver.phie_i->set(0.0);
146 collect_lead_names(elec.stimuli);
148 int num_leads = lead_names.size();
150 log_msg(0, 4, 0,
"Warning: No Leadfield stimuli found. Skipping.");
156 Z.resize(num_leads,
nullptr);
157 for(
int i = 0; i < num_leads; i++)
164 log_msg(0, 0, 0,
"Computing %d leadfield matrices...", num_leads);
166 for(
int lead_idx = 0; lead_idx < num_leads; lead_idx++) {
167 std::string current_lead = lead_names[lead_idx];
168 log_msg(0, 1, 0,
" - Solving lead %d/%d: %s", lead_idx + 1, num_leads, current_lead.c_str());
170 apply_unit_curr_lead(current_lead, elec);
171 (*elec.ellip_solver.lin_solver)(*elec.ellip_solver.phie, *elec.ellip_solver.phiesrc);
173 if(elec.ellip_solver.lin_solver->reason < 0) {
174 log_msg(0, 5, 0,
"Error: Leadfield solver diverged for lead %s!", current_lead.c_str());
178 i2e->
backward(*elec.ellip_solver.phie, *elec.ellip_solver.phie_i);
182 Z[lead_idx]->deep_copy(*elec.ellip_solver.phie_i);
184 if(p2c) (*p2c)(*elec.ellip_solver.phie_i,
true);
185 write_leadfield_igb(current_lead, elec.ellip_solver.phie_i);
187 log_msg(0, 0, 0,
"Leadfield computation complete.");
190 void Leadfield::load(Electrics & elec)
192 collect_lead_names(elec.stimuli);
194 int num_leads = lead_names.size();
196 log_msg(0, 5, 0,
"Error: 'load' called but no leads defined in config!");
202 Z.resize(num_leads,
nullptr);
203 for(
int i = 0; i < num_leads; i++)
207 std::string dir = lf_dir;
208 if(!dir.empty() && dir.back() !=
'/') dir +=
"/";
210 size_t global_nodes = Z[0]->gsize();
215 for(
int i = 0; i < num_leads; i++) {
216 std::string name = lead_names[i];
217 std::string fpath = dir +
"LF_" + name +
".igb";
221 f = fopen(fpath.c_str(),
"r");
223 log_msg(0, 5, 0,
"Error: Cannot open file %s", fpath.c_str());
227 if(igb.type() == 0) {
228 log_msg(0, 5, 0,
"Error: Corrupt or unrecognized IGB header in %s", fpath.c_str());
229 fclose(f); f =
nullptr; err = 1;
230 }
else if((
size_t)igb.x() != global_nodes) {
231 log_msg(0, 5, 0,
"Error: Node count mismatch in %s! Expected %zu, got %d",
232 fpath.c_str(), global_nodes, igb.x());
233 fclose(f); f =
nullptr; err = 1;
239 size_t nread = Z[i]->read_binary<
float>(f);
241 if(nread != Z[i]->gsize()) {
242 log_msg(0, 5, 0,
"Error: Incomplete read from %s! Expected %zu elements, got %zu.",
243 fpath.c_str(), Z[i]->gsize(), nread);
246 if(p2c) (*p2c)(*Z[i],
false);
248 log_msg(0, 0, 0,
" - Loaded %s", fpath.c_str());
254 if(Z.empty())
return;
262 for(
size_t i = 0; i < Z.size(); i++) {
263 double ecg = Z[i]->dot(*Im);
265 trace_data[i].push_back((
float)ecg);
272 log_msg(0, 5, 0,
"Error: Leadfield requires active EP physics! Aborting.");
278 MPI_Barrier(PETSC_COMM_WORLD);
280 if(lf_dir.length() > 0) {
283 log_msg(0, 0, 0,
"Computing leadfields...");
287 int num_leads = lead_names.size();
289 log_msg(0, 5, 0,
"No leads detected. Exiting.");
294 trace_data.resize(num_leads);
295 for(
auto & vec : trace_data) vec.clear();
303 int total_frames = 0;
316 total_frames = head.
t();
317 file_dt = head.
inc_t();
319 if((
size_t)head.
x() != vm_step->
gsize()) {
320 log_msg(0, 5, 0,
"Error: Vm dimension in %s does not match mesh. Aborting.", vm_file.c_str());
325 stride = (int)((ecg_timedt / file_dt) + 0.5);
326 if(stride < 1) stride = 1;
328 log_msg(0, 1, 0,
" VM file: dt=%.4f ms, target=%.4f ms, stride=%d",
329 file_dt, ecg_timedt, stride);
333 log_msg(0, 5, 0,
"Error: Could not open %s", vm_file.c_str());
340 MPI_Bcast(&total_frames, 1, MPI_INT, 0, PETSC_COMM_WORLD);
341 MPI_Bcast(&file_dt, 1, MPI_FLOAT, 0, PETSC_COMM_WORLD);
342 MPI_Bcast(&stride, 1, MPI_INT, 0, PETSC_COMM_WORLD);
344 if(total_frames == 0) {
349 log_msg(0, 0, 0,
"Computing ECG for %d leads, %d frames...", num_leads, total_frames);
352 for(
int t = 0; t < total_frames; t++) {
354 if(nread != vm_step->
gsize()) {
355 log_msg(0, 5, 0,
"Error: %s read incomplete data slice from %s!", __func__, vm_file.c_str());
359 bool forward =
false;
360 if(sc) (*sc)(*vm_step, forward);
366 if(
get_rank() == 0 && fd) fclose(fd);
371 for(
size_t i = 0; i < lead_names.size(); i++) {
372 std::string fname =
"ECG_" + lead_names[i] +
".dat";
373 FILE* f = fopen(fname.c_str(),
"w");
375 for(
size_t t = 0; t < trace_data[i].size(); t++) {
376 double time = t * file_dt * stride;
377 fprintf(f,
"%g %g\n", time, trace_data[i][t]);
381 log_msg(0, 5, 0,
"Error: Cannot write %s", fname.c_str());
388 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.
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.