def check_periodic(self, varTc, varTc_old, eps, check, cyclerr): if isinstance(varTc, np.ndarray): varTc_sq, varTc_old_sq = varTc, varTc_old else: varTc_sq, varTc_old_sq = allgather_vec(varTc, self.comm), allgather_vec(varTc_old, self.comm) if check=='allvar': vals = [] for i in range(len(varTc_sq)): vals.append( math.fabs((varTc_sq[i]-varTc_old_sq[i])/max(1.0,math.fabs(varTc_old_sq[i]))) ) elif check=='pQvar': vals = [] pQvar_ids = [1-self.si[2],3-self.si[0],4,6,10-self.si[3],12-self.si[1],13,15] for i in range(len(varTc_sq)): if i in pQvar_ids: vals.append( math.fabs((varTc_sq[i]-varTc_old_sq[i])/max(1.0,math.fabs(varTc_old_sq[i]))) ) else: raise NameError("Unknown check option!") cyclerr[0] = max(vals) if cyclerr[0] <= eps: is_periodic = True else: is_periodic = False return is_periodic
def write_initial(self, path, nm, varTc_old, varTc): if isinstance(varTc_old, np.ndarray): varTc_old_sq, varTc_sq = varTc_old, varTc else: varTc_old_sq, varTc_sq = allgather_vec(varTc_old, self.comm), allgather_vec( varTc, self.comm) if self.comm.rank == 0: filename1 = path + '/initial_data_' + nm + '_Tstart.txt' # conditions at beginning of cycle f1 = open(filename1, 'wt') filename2 = path + '/initial_data_' + nm + '_Tend.txt' # conditions at end of cycle f2 = open(filename2, 'wt') for i in range(len(self.varmap)): f1.write('%s %.16E\n' % (list(self.varmap.keys())[i] + '_0', varTc_old_sq[list(self.varmap.values())[i]])) f2.write('%s %.16E\n' % (list(self.varmap.keys())[i] + '_0', varTc_sq[list( self.varmap.values())[i]])) f1.close() f2.close()
def print_to_screen(self, var, aux): if isinstance(var, np.ndarray): var_sq = var else: var_sq = allgather_vec(var, self.comm) nc = len(self.c_) if self.comm.rank == 0: print("Output of 0D vascular model (syspul):") for i in range(nc): print('{:<9s}{:<3s}{:<10.3f}'.format( list(self.auxmap.keys())[i], ' = ', aux[list(self.auxmap.values())[i]])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format( '' + self.vname_prfx[2] + '_at_l', ' = ', var_sq[self.varmap['' + self.vname_prfx[2] + '_at_l']], ' ', '' + self.vname_prfx[3] + '_at_r', ' = ', var_sq[self.varmap['' + self.vname_prfx[3] + '_at_r']])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format( '' + self.vname_prfx[0] + '_v_l', ' = ', var_sq[self.varmap['' + self.vname_prfx[0] + '_v_l']], ' ', '' + self.vname_prfx[1] + '_v_r', ' = ', var_sq[self.varmap['' + self.vname_prfx[1] + '_v_r']])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format( 'p_ar_sys', ' = ', var_sq[self.varmap['p_ar_sys']], ' ', 'p_ar_pul', ' = ', var_sq[self.varmap['p_ar_pul']])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format( 'p_ven_sys', ' = ', var_sq[self.varmap['p_ven_sys']], ' ', 'p_ven_pul', ' = ', var_sq[self.varmap['p_ven_pul']])) sys.stdout.flush()
def write_output(self, path, t, var, aux, nm=''): if isinstance(var, np.ndarray): var_sq = var else: var_sq = allgather_vec(var, self.comm) # mode: 'wt' generates new file, 'a' appends to existing one if self.init: mode = 'wt' else: mode = 'a' self.init = False if self.comm.rank == 0: for i in range(len(self.varmap)): filename = path + '/results_' + nm + '_' + list( self.varmap.keys())[i] + '.txt' f = open(filename, mode) f.write('%.16E %.16E\n' % (t, var_sq[list(self.varmap.values())[i]])) f.close() for i in range(len(self.auxmap)): filename = path + '/results_' + nm + '_' + list( self.auxmap.keys())[i] + '.txt' f = open(filename, mode) f.write('%.16E %.16E\n' % (t, aux[list(self.auxmap.values())[i]])) f.close()
def check_periodic(self, varTc, varTc_old, eps, check, cyclerr): if isinstance(varTc, np.ndarray): varTc_sq, varTc_old_sq = varTc, varTc_old else: varTc_sq, varTc_old_sq = allgather_vec(varTc, self.comm), allgather_vec( varTc_old, self.comm) if check == 'allvar': vals = [] for i in range(len(varTc_sq)): vals.append( math.fabs((varTc_sq[i] - varTc_old_sq[i]) / max(1.0, math.fabs(varTc_old_sq[i])))) elif check == 'pQvar': vals = [] pQvar_ids = [ self.varmap['' + self.vname_prfx[2] + '_at_l'], self.varmap['' + self.vname_prfx[0] + '_v_l'], self.varmap['p_ar_sys'], self.varmap['p_ven_sys'], self.varmap['' + self.vname_prfx[3] + '_at_r'], self.varmap['' + self.vname_prfx[1] + '_v_r'], self.varmap['p_ar_pul'], self.varmap['p_ven_pul'] ] for i in range(len(varTc_sq)): if i in pQvar_ids: vals.append( math.fabs((varTc_sq[i] - varTc_old_sq[i]) / max(1.0, math.fabs(varTc_old_sq[i])))) else: raise NameError("Unknown check option!") cyclerr[0] = max(vals) if cyclerr[0] <= eps: is_periodic = True else: is_periodic = False return is_periodic
def print_to_screen(self, var, aux): if isinstance(var, np.ndarray): var_sq = var else: var_sq = allgather_vec(var, self.comm) if self.comm.rank == 0: print("Output of 0D model (2elwindkessel):") print('{:<1s}{:<3s}{:<10.3f}'.format(self.cname, ' = ', aux[0])) print('{:<1s}{:<3s}{:<10.3f}'.format(self.vname, ' = ', var_sq[0])) sys.stdout.flush()
def print_to_screen(self, x, a): if isinstance(x, np.ndarray): x_sq = x else: x_sq = allgather_vec(x, self.comm) if self.comm.rank == 0: print("Output of 0D model (4elwindkesselLsZ):") print('{:<1s}{:<3s}{:<10.3f}'.format(self.cname, ' = ', a[0])) print('{:<1s}{:<3s}{:<10.3f}'.format('p', ' = ', x_sq[0])) print('{:<1s}{:<3s}{:<10.3f}'.format('q', ' = ', x_sq[1])) sys.stdout.flush()
def write_restart(self, path, nm, N, var): if isinstance(var, np.ndarray): var_sq = var else: var_sq = allgather_vec(var, self.comm) if self.comm.rank == 0: filename = path + '/checkpoint_' + nm + '_' + str(N) + '.txt' f = open(filename, 'wt') for i in range(len(var_sq)): f.write('%.16E\n' % (var_sq[i])) f.close()
def evaluate(self, x, t, df=None, f=None, dK=None, K=None, c=[], y=[], a=None, fnc=[]): if isinstance(x, np.ndarray): x_sq = x else: x_sq = allgather_vec(x, self.comm) # ODE lhs (time derivative) residual part df if df is not None: for i in range(self.numdof): df[i] = self.df__[i](x_sq, c, t, fnc) # ODE rhs residual part f if f is not None: for i in range(self.numdof): f[i] = self.f__[i](x_sq, c, t, fnc) # ODE lhs (time derivative) stiffness part dK (ddf/dx) if dK is not None: for i in range(self.numdof): for j in range(self.numdof): dK[i, j] = self.dK__[i][j](x_sq, c, t, fnc) # ODE rhs stiffness part K (df/dx) if K is not None: for i in range(self.numdof): for j in range(self.numdof): K[i, j] = self.K__[i][j](x_sq, c, t, fnc) # auxiliary variable vector a (for post-processing or periodic state check) if a is not None: for i in range(self.numdof): a[i] = self.a__[i](x_sq, c, t, fnc)
def print_to_screen(self, var, aux): if isinstance(var, np.ndarray): var_sq = var else: var_sq = allgather_vec(var, self.comm) if self.comm.rank == 0: print("Output of 0D vascular model (syspul_veins):") print('{:<9s}{:<3s}{:<10.1f}{:<3s}{:<9s}{:<3s}{:<10.1f}'.format(''+self.cname_prfx[2]+'_at_l',' = ',aux[0],' ',''+self.cname_prfx[3]+'_at_r',' = ',aux[9])) print('{:<9s}{:<3s}{:<10.1f}{:<3s}{:<9s}{:<3s}{:<10.1f}'.format(''+self.cname_prfx[0]+'_v_l',' = ',aux[2],' ',''+self.cname_prfx[1]+'_v_r',' = ',aux[11])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format(''+self.vname_prfx[2]+'_at_l',' = ',var_sq[1-self.si[2]],' ',''+self.vname_prfx[3]+'_at_r',' = ',var_sq[10-self.si[3]])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format(''+self.vname_prfx[0]+'_v_l',' = ',var_sq[3-self.si[0]],' ',''+self.vname_prfx[1]+'_v_r',' = ',var_sq[12-self.si[1]])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format('p_ar_sys',' = ',var_sq[4],' ','p_ar_pul',' = ',var_sq[13])) print('{:<9s}{:<3s}{:<10.3f}{:<3s}{:<9s}{:<3s}{:<10.3f}'.format('p_ven_sys',' = ',var_sq[6],' ','p_ven_pul',' = ',var_sq[15])) sys.stdout.flush()
def results_check_vec(vec, vec_corr, comm, tol=1.0e-6): success = True vec_sq = allgather_vec(vec, comm) errs = np.zeros(len(vec_sq)) for i in range(len(vec_sq)): errs[i] = abs(vec_sq[i] - vec_corr[i]) if errs[i] > tol: success = False for i in range(len(vec_sq)): if comm.rank == 0: print("vec[%i] = %.16E, CORR = %E, err = %E" % (i, vec_sq[i], vec_corr[i], errs[i])) sys.stdout.flush() return success
def evaluate(self, x, dt, t, df=None, f=None, K=None, c=[], y=[], a=None, fnc=[]): if isinstance(x, np.ndarray): x_sq = x else: x_sq = allgather_vec(x, self.comm) # rhs part df if df is not None: for i in range(self.numdof): df[i] = self.df__[i](x_sq, c, t, fnc) # rhs part f if f is not None: for i in range(self.numdof): f[i] = self.f__[i](x_sq, c, t, fnc) # stiffness matrix K if K is not None: for i in range(self.numdof): for j in range(self.numdof): K[i, j] = self.Kdf__[i][j]( x_sq, c, t, fnc) / dt + self.Kf__[i][j]( x_sq, c, t, fnc) * self.theta # auxiliary variable vector a (for post-processing or periodic state check) if a is not None: for i in range(self.numdof): a[i] = self.a__[i](x_sq, c, t, fnc)
def results_check_node(u, check_node, u_corr, V, comm, tol=1.0e-6, nm='vec'): success = True # block size of vector to check bs = u.vector.getBlockSize() # computed errors (difference between simulation and expected results) errs = np.zeros(bs * len(check_node)) # dof coordinates co = V.tabulate_dof_coordinates() # index map im = V.dofmap.index_map.global_indices() # in parallel, dof indices can be ordered differently, so we need to check the position of the node in the # re-ordered local co array and then grep out the corresponding dof index from the index map dof_indices, dof_indices_gathered = {}, [] for i in range(len(check_node)): ind = np.where((np.round(check_node[i], 8) == np.round(co, 8)).all(axis=1))[0] if len(ind): dof_indices[i] = im[ind[0]] # gather indices dof_indices_gathered = comm.allgather(dof_indices) # make a flat and ordered list of indices (may still have duplicates) dof_indices_flat = [] for i in range(len(check_node)): for l in range(len(dof_indices_gathered)): if i in dof_indices_gathered[l].keys(): dof_indices_flat.append(dof_indices_gathered[l][i]) # create unique list (remove duplicates, but keep order) dof_indices_unique = list(dict.fromkeys(dof_indices_flat)) # gather vector to check u_sq = allgather_vec(u.vector, comm) for i in range(len(check_node)): for j in range(bs): errs[bs * i + j] = abs(u_sq[bs * dof_indices_unique[i] + j] - u_corr[bs * i + j]) if errs[bs * i + j] > tol: success = False for i in range(len(check_node)): for j in range(bs): if comm.rank == 0: print("" + nm + "[%i] = %.16E, CORR = %.16E, err = %.16E" % (bs * i + j, u_sq[bs * dof_indices_unique[i] + j], u_corr[bs * i + j], errs[bs * i + j])) sys.stdout.flush() if comm.rank == 0: print("Max error: %E" % (max(errs))) sys.stdout.flush() return success
def solve_problem(self): start = time.time() # print header utilities.print_problem(self.pb.problem_physics, self.pb.pbs.comm, self.pb.pbs.ndof) # read restart information if self.pb.pbs.restart_step > 0: self.pb.pbs.io.readcheckpoint(self.pb.pbs, self.pb.pbs.restart_step) self.pb.pbs.simname += '_r' + str(self.pb.pbs.restart_step) self.pb.set_pressure_fem(self.pb.lm_old, self.pb.coupfuncs_old) # in case we want to prestress with MULF (Gee et al. 2010) prior to solving the 3D-0D problem if self.pb.pbs.prestress_initial and self.pb.pbs.restart_step == 0: # solve solid prestress problem self.solverprestr.solve_initial_prestress() del self.solverprestr else: # set flag definitely to False if we're restarting self.pb.pbs.prestress_initial = False self.pb.constr, self.pb.constr_old = [], [] for i in range(self.pb.num_coupling_surf): lm_sq, lm_old_sq = allgather_vec(self.pb.lm, self.pb.comm), allgather_vec( self.pb.lm_old, self.pb.comm) con = assemble_scalar(self.pb.cq[i]) con = self.pb.pbs.comm.allgather(con) self.pb.constr.append(sum(con)) self.pb.constr_old.append(sum(con)) # consider consistent initial acceleration if self.pb.pbs.timint != 'static' and self.pb.pbs.restart_step == 0: # weak form at initial state for consistent initial acceleration solve weakform_a = self.pb.pbs.deltaW_kin_old + self.pb.pbs.deltaW_int_old - self.pb.pbs.deltaW_ext_old - self.pb.work_coupling_old jac_a = derivative(weakform_a, self.pb.pbs.a_old, self.pb.pbs.du) # actually linear in a_old # solve for consistent initial acceleration a_old self.solnln.solve_consistent_ini_acc(weakform_a, jac_a, self.pb.pbs.a_old) # write mesh output self.pb.pbs.io.write_output(self.pb.pbs, writemesh=True) # solid constraint main time loop for N in range(self.pb.pbs.restart_step + 1, self.pb.pbs.numstep_stop + 1): wts = time.time() # current time t = N * self.pb.pbs.dt # set time-dependent functions self.pb.pbs.ti.set_time_funcs(self.pb.pbs.ti.funcs_to_update, self.pb.pbs.ti.funcs_to_update_vec, t) # take care of active stress if self.pb.pbs.have_active_stress and self.pb.pbs.active_stress_trig == 'ode': self.pb.pbs.evaluate_active_stress_ode(t) # solve self.solnln.newton(self.pb.pbs.u, self.pb.pbs.p, self.pb.lm, t, locvar=self.pb.pbs.theta, locresform=self.pb.pbs.r_growth, locincrform=self.pb.pbs.del_theta) # update time step self.pb.pbs.ti.update_timestep( self.pb.pbs.u, self.pb.pbs.u_old, self.pb.pbs.v_old, self.pb.pbs.a_old, self.pb.pbs.p, self.pb.pbs.p_old, self.pb.pbs.internalvars, self.pb.pbs.internalvars_old, self.pb.pbs.ti.funcs_to_update, self.pb.pbs.ti.funcs_to_update_old, self.pb.pbs.ti.funcs_to_update_vec, self.pb.pbs.ti.funcs_to_update_vec_old) # update old pressures on solid self.pb.lm.assemble(), self.pb.lm_old.axpby(1.0, 0.0, self.pb.lm) self.pb.set_pressure_fem(self.pb.lm_old, self.pb.coupfuncs_old) # update old 3D constraint variable for i in range(self.pb.num_coupling_surf): self.pb.constr_old[i] = self.pb.constr[i] # solve time for time step wte = time.time() wt = wte - wts # print time step info to screen self.pb.pbs.ti.print_timestep(N, t, wt=wt) # write output and restart info self.pb.pbs.io.write_output(self.pb.pbs, N=N, t=t) if self.pb.comm.rank == 0: # only proc 0 should print this print('Time for computation: %.4f s (= %.2f min)' % (time.time() - start, (time.time() - start) / 60.)) sys.stdout.flush()
def solve_problem(self): start = time.time() # print header utilities.print_problem(self.pb.problem_physics, self.pb.pbs.comm, self.pb.pbs.ndof) # read restart information if self.pb.pbs.restart_step > 0: self.pb.pbs.io.readcheckpoint(self.pb.pbs, self.pb.pbs.restart_step) self.pb.pbf.readrestart(self.pb.pbs.simname, self.pb.pbs.restart_step) self.pb.pbs.simname += '_r' + str(self.pb.pbs.restart_step) # set pressure functions for old state - s_old already initialized by 0D flow problem if self.pb.coupling_type == 'monolithic_direct': self.pb.pbf.cardvasc0D.set_pressure_fem( self.pb.pbf.s_old, self.pb.pbf.cardvasc0D.v_ids, self.pb.pr0D, self.pb.coupfuncs_old) if self.pb.coupling_type == 'monolithic_lagrange': self.pb.pbf.cardvasc0D.set_pressure_fem( self.pb.lm_old, list(range(self.pb.num_coupling_surf)), self.pb.pr0D, self.pb.coupfuncs_old) if self.pb.coupling_type == 'monolithic_direct': # old 3D coupling quantities (volumes or fluxes) for i in range(self.pb.num_coupling_surf): cq = assemble_scalar(self.pb.cq_old[i]) cq = self.pb.pbs.comm.allgather(cq) self.pb.pbf.c.append(sum(cq) * self.pb.cq_factor[i]) if self.pb.coupling_type == 'monolithic_lagrange': for i in range(self.pb.num_coupling_surf): lm_sq, lm_old_sq = allgather_vec(self.pb.lm, self.pb.comm), allgather_vec( self.pb.lm_old, self.pb.comm) self.pb.pbf.c.append(lm_sq[i]) con = assemble_scalar(self.pb.cq_old[i]) con = self.pb.pbs.comm.allgather(con) self.pb.constr.append(sum(con) * self.pb.cq_factor[i]) self.pb.constr_old.append(sum(con) * self.pb.cq_factor[i]) if bool(self.pb.pbf.chamber_models): self.pb.pbf.y = [] for ch in self.pb.pbf.chamber_models: if self.pb.pbf.chamber_models[ch]['type'] == '0D_elast': self.pb.pbf.y.append( self.pb.pbs.ti.timecurves( self.pb.pbf.chamber_models[ch]['activation_curve']) (self.pb.pbs.t_init)) # initially evaluate 0D model at old state self.pb.pbf.cardvasc0D.evaluate(self.pb.pbf.s_old, self.pb.pbs.dt, self.pb.pbs.t_init, self.pb.pbf.df_old, self.pb.pbf.f_old, None, self.pb.pbf.c, self.pb.pbf.y, self.pb.pbf.aux_old) # consider consistent initial acceleration if self.pb.pbs.timint != 'static' and self.pb.pbs.restart_step == 0: # weak form at initial state for consistent initial acceleration solve weakform_a = self.pb.pbs.deltaP_kin_old + self.pb.pbs.deltaP_int_old - self.pb.pbs.deltaP_ext_old - self.pb.power_coupling_old jac_a = derivative(weakform_a, self.pb.pbs.a_old, self.pb.pbs.dv) # actually linear in a_old # solve for consistent initial acceleration a_old self.solnln.solve_consistent_ini_acc(weakform_a, jac_a, self.pb.pbs.a_old) # write mesh output self.pb.pbs.io.write_output(self.pb.pbs, writemesh=True) # fluid 0D flow main time loop for N in range(self.pb.restart_step + 1, self.pb.numstep_stop + 1): wts = time.time() # current time t = N * self.pb.pbs.dt # offset time for multiple cardiac cycles t_off = ( self.pb.pbf.ti.cycle[0] - 1 ) * self.pb.pbf.cardvasc0D.T_cycl # zero if T_cycl variable is not specified # set time-dependent functions self.pb.pbs.ti.set_time_funcs(self.pb.pbs.ti.funcs_to_update, self.pb.pbs.ti.funcs_to_update_vec, t - t_off) # activation curves for 0D chambers (if present) self.pb.pbf.evaluate_activation(t - t_off) # solve self.solnln.newton(self.pb.pbs.v, self.pb.pbs.p, self.pb.pbf.s, t - t_off) # get midpoint dof values for post-processing (has to be called before update!) self.pb.pbf.cardvasc0D.midpoint_avg( self.pb.pbf.s, self.pb.pbf.s_old, self.pb.pbf.s_mid), self.pb.pbf.cardvasc0D.midpoint_avg( self.pb.pbf.aux, self.pb.pbf.aux_old, self.pb.pbf.aux_mid) # update time step - fluid and 0D model self.pb.pbs.ti.update_timestep( self.pb.pbs.v, self.pb.pbs.v_old, self.pb.pbs.a_old, self.pb.pbs.p, self.pb.pbs.p_old, self.pb.pbs.ti.funcs_to_update, self.pb.pbs.ti.funcs_to_update_old, self.pb.pbs.ti.funcs_to_update_vec, self.pb.pbs.ti.funcs_to_update_vec_old) self.pb.pbf.cardvasc0D.update(self.pb.pbf.s, self.pb.pbf.df, self.pb.pbf.f, self.pb.pbf.s_old, self.pb.pbf.df_old, self.pb.pbf.f_old, self.pb.pbf.aux, self.pb.pbf.aux_old) # update old pressures on fluid if self.pb.coupling_type == 'monolithic_direct': self.pb.pbf.cardvasc0D.set_pressure_fem( self.pb.pbf.s_old, self.pb.pbf.cardvasc0D.v_ids, self.pb.pr0D, self.pb.coupfuncs_old) if self.pb.coupling_type == 'monolithic_lagrange': self.pb.lm.assemble(), self.pb.lm_old.axpby( 1.0, 0.0, self.pb.lm) self.pb.pbf.cardvasc0D.set_pressure_fem( self.pb.lm_old, list(range(self.pb.num_coupling_surf)), self.pb.pr0D, self.pb.coupfuncs_old) # update old 3D fluxes for i in range(self.pb.num_coupling_surf): self.pb.constr_old[i] = self.pb.constr[i] # solve time for time step wte = time.time() wt = wte - wts # print to screen self.pb.pbf.cardvasc0D.print_to_screen(self.pb.pbf.s_mid, self.pb.pbf.aux_mid) # print time step info to screen self.pb.pbf.ti.print_timestep(N, t, self.pb.pbs.numstep, wt=wt) # check for periodicity in cardiac cycle and stop if reached (only for syspul* models - cycle counter gets updated here) is_periodic = self.pb.pbf.cardvasc0D.cycle_check( self.pb.pbf.s, self.pb.pbf.sTc, self.pb.pbf.sTc_old, t - t_off, self.pb.pbf.ti.cycle, self.pb.pbf.ti.cycleerror, self.pb.pbf.eps_periodic, check=self.pb.pbf.periodic_checktype, inioutpath=self.pb.pbf.output_path_0D, nm=self.pb.pbs.simname, induce_pert_after_cycl=self.pb.pbf.perturb_after_cylce) # induce some disease/perturbation for cardiac cycle (i.e. valve stenosis or leakage) if self.pb.pbf.perturb_type is not None and not self.pb.pbf.have_induced_pert: self.pb.induce_perturbation() # write output and restart info self.pb.pbs.io.write_output(self.pb.pbs, N=N, t=t) # raw txt file output of 0D model quantities if self.pb.pbf.write_results_every_0D > 0 and N % self.pb.pbf.write_results_every_0D == 0: self.pb.pbf.cardvasc0D.write_output(self.pb.pbf.output_path_0D, t, self.pb.pbf.s_mid, self.pb.pbf.aux_mid, self.pb.pbs.simname) # write 0D restart info - old and new quantities are the same at this stage (except cycle values sTc) if self.pb.pbs.io.write_restart_every > 0 and N % self.pb.pbs.io.write_restart_every == 0: self.pb.pbf.writerestart(self.pb.pbs.simname, N) if is_periodic: if self.pb.comm.rank == 0: print( "Periodicity reached after %i heart cycles with cycle error %.4f! Finished. :-)" % (self.pb.pbf.ti.cycle[0] - 1, self.pb.pbf.ti.cycleerror[0])) sys.stdout.flush() break if self.pb.comm.rank == 0: # only proc 0 should print this print('Time for computation: %.4f s (= %.2f min)' % (time.time() - start, (time.time() - start) / 60.)) sys.stdout.flush()