Ejemplo n.º 1
0
    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()
Ejemplo n.º 2
0
    def solve_problem(self):
        
        start = time.time()

        # print header
        utilities.print_problem(self.pb.problem_physics, self.pb.comm, self.pb.ndof)

        # read restart information
        if self.pb.restart_step > 0:
            self.pb.io.readcheckpoint(self.pb, self.pb.restart_step)
            self.pb.simname += '_r'+str(self.pb.restart_step)

        # in case we want to prestress with MULF (Gee et al. 2010) prior to solving the full solid problem
        if self.pb.prestress_initial and self.pb.restart_step == 0:

            self.solve_initial_prestress()

        else:
            # set flag definitely to False if we're restarting
            self.pb.prestress_initial = False

        # consider consistent initial acceleration
        if self.pb.timint != 'static' and self.pb.restart_step == 0:
            # weak form at initial state for consistent initial acceleration solve
            weakform_a = self.pb.deltaW_kin_old + self.pb.deltaW_int_old - self.pb.deltaW_ext_old
            
            jac_a = derivative(weakform_a, self.pb.a_old, self.pb.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.a_old)

        # write mesh output
        self.pb.io.write_output(self.pb, writemesh=True)
        
        
        # solid 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.dt

            # set time-dependent functions
            self.pb.ti.set_time_funcs(self.pb.ti.funcs_to_update, self.pb.ti.funcs_to_update_vec, t)
            
            # take care of active stress
            if self.pb.have_active_stress and self.pb.active_stress_trig == 'ode':
                self.pb.evaluate_active_stress_ode(t)

            # solve
            self.solnln.newton(self.pb.u, self.pb.p, locvar=self.pb.theta, locresform=self.pb.r_growth, locincrform=self.pb.del_theta)

            # compute the growth rate (has to be called before update_timestep)
            if self.pb.have_growth:
                self.pb.compute_solid_growth_rate(N, t)

            # write output
            self.pb.io.write_output(self.pb, N=N, t=t)
            
            # update - displacement, velocity, acceleration, pressure, all internal variables, all time functions
            self.pb.ti.update_timestep(self.pb.u, self.pb.u_old, self.pb.v_old, self.pb.a_old, self.pb.p, self.pb.p_old, self.pb.internalvars, self.pb.internalvars_old, self.pb.ti.funcs_to_update, self.pb.ti.funcs_to_update_old, self.pb.ti.funcs_to_update_vec, self.pb.ti.funcs_to_update_vec_old)

            # solve time for time step
            wte = time.time()
            wt = wte - wts

            # print time step info to screen
            self.pb.ti.print_timestep(N, t, wt=wt)

            # write restart info - old and new quantities are the same at this stage
            self.pb.io.write_restart(self.pb, N)

            if self.pb.problem_type == 'solid_flow0d_multiscale_gandr' and abs(self.pb.growth_rate) <= self.pb.tol_stop_large:
                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()
    def solve_problem(self):

        start = time.time()

        # print header
        utilities.print_problem(self.pb.problem_physics, self.pb.comm)

        # multiscale growth and remodeling solid 0D flow main time loop
        for N in range(self.pb.restart_cycle + 1, self.pb.N_cycles + 1):

            wts = time.time()

            # time offset from previous small scale times
            self.pb.pbsmall.t_prev = (self.pb.pbsmall.pbf.ti.cycle[0] - 1
                                      ) * self.pb.pbsmall.pbf.cardvasc0D.T_cycl

            # change output names
            self.pb.pbsmall.pbs.simname = self.pb.simname_small + str(N)
            self.pb.pblarge.simname = self.pb.simname_large + str(N)

            self.set_state_small()

            if not self.pb.restart_from_small:

                if self.pb.comm.rank == 0:
                    print(
                        "Solving small scale 3D-0D coupled solid-flow0d problem:"
                    )
                    sys.stdout.flush()

                # solve small scale 3D-0D coupled solid-flow0d problem with fixed growth
                self.solversmall.solve_problem()

                # next small scale run is a resumption of a previous one
                self.pb.pbsmall.restart_multiscale = True

                if self.pb.write_checkpoints:
                    # write checkpoint for potential restarts
                    self.pb.pbsmall.pbs.io.writecheckpoint(
                        self.pb.pbsmall.pbs, N)
                    self.pb.pbsmall.pbf.writerestart(
                        self.pb.pbsmall.pbs.simname, N, ms=True)

            else:

                # read small scale checkpoint if we restart from this scale
                self.pb.pbsmall.pbs.io.readcheckpoint(
                    self.pb.pbsmall.pbs, self.pb.restart_cycle + 1)
                self.pb.pbsmall.pbf.readrestart(self.pb.pbsmall.pbs.simname,
                                                self.pb.restart_cycle + 1,
                                                ms=True)
                # induce the perturbation
                if not self.pb.pbsmall.pbf.have_induced_pert:
                    self.pb.pbsmall.induce_perturbation()
                # no need to do after restart
                self.pb.pbsmall.pbs.prestress_initial = False
                # set flag to False again
                self.pb.restart_from_small = False
                # next small scale run is a resumption of a previous one
                self.pb.pbsmall.restart_multiscale = True

            # set large scale state
            self.set_state_large(N)

            # compute volume prior to G&R
            vol_prior = self.compute_volume_large()

            if self.pb.comm.rank == 0:
                print(
                    "Solving large scale solid growth and remodeling problem:")
                sys.stdout.flush()

            # solve large scale static G&R solid problem with fixed loads
            self.solverlarge.solve_problem()

            # compute volume after G&R
            vol_after = self.compute_volume_large()

            if self.pb.write_checkpoints:
                # write checkpoint for potential restarts
                self.pb.pblarge.io.writecheckpoint(self.pb.pblarge, N)

            # relative volume increase over large scale run
            volchange = (vol_after - vol_prior) / vol_prior
            if self.pb.comm.rank == 0:
                print('Volume change due to growth: %.4e' % (volchange))
                sys.stdout.flush()

            # check if below tolerance
            if abs(volchange) <= self.pb.tol_outer:
                break

        if self.pb.comm.rank == 0:  # only proc 0 should print this
            print('Time for full multiscale computation: %.4f s (= %.2f min)' %
                  (time.time() - start, (time.time() - start) / 60.))
            sys.stdout.flush()
Ejemplo n.º 4
0
    def solve_problem(self):

        start = time.time()

        # print header
        utilities.print_problem(self.pb.problem_physics, self.pb.comm,
                                self.pb.ndof)

        # read restart information
        if self.pb.restart_step > 0:
            self.pb.io.readcheckpoint(self.pb, self.pb.restart_step)
            self.pb.simname += '_r' + str(self.pb.restart_step)

        # consider consistent initial acceleration
        if self.pb.timint != 'static' and self.pb.restart_step == 0:
            # weak form at initial state for consistent initial acceleration solve
            weakform_a = self.pb.deltaP_kin_old + self.pb.deltaP_int_old - self.pb.deltaP_ext_old

            jac_a = derivative(weakform_a, self.pb.a_old,
                               self.pb.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.a_old)

        # write mesh output
        self.pb.io.write_output(self.pb, writemesh=True)

        # fluid 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.dt

            # set time-dependent functions
            self.pb.ti.set_time_funcs(self.pb.ti.funcs_to_update,
                                      self.pb.ti.funcs_to_update_vec, t)

            # solve
            self.solnln.newton(self.pb.v, self.pb.p)

            # write output
            self.pb.io.write_output(self.pb, N=N, t=t)

            # update
            self.pb.ti.update_timestep(self.pb.v, self.pb.v_old, self.pb.a_old,
                                       self.pb.p, self.pb.p_old,
                                       self.pb.ti.funcs_to_update,
                                       self.pb.ti.funcs_to_update_old,
                                       self.pb.ti.funcs_to_update_vec,
                                       self.pb.ti.funcs_to_update_vec_old)

            # solve time for time step
            wte = time.time()
            wt = wte - wts

            # print time step info to screen
            self.pb.ti.print_timestep(N, t, wt=wt)

            # write restart info - old and new quantities are the same at this stage
            self.pb.io.write_restart(self.pb, N)

        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()
Ejemplo n.º 5
0
    def solve_problem(self):

        start = time.time()

        # print header
        utilities.print_problem(self.pb.problem_type, self.pb.comm,
                                self.pb.cardvasc0D.numdof)

        # read restart information
        if self.pb.restart_step > 0:
            self.pb.readrestart(self.pb.simname, self.pb.restart_step)
            self.pb.simname += '_r' + str(self.pb.restart_step)

        # evaluate old state
        if self.pb.excitation_curve is not None:
            self.pb.c = []
            self.pb.c.append(
                self.pb.ti.timecurves(self.pb.excitation_curve)(
                    self.pb.t_init))
        if bool(self.pb.chamber_models):
            self.pb.y = []
            for ch in ['lv', 'rv', 'la', 'ra']:
                if self.pb.chamber_models[ch]['type'] == '0D_elast':
                    self.pb.y.append(
                        self.pb.ti.timecurves(
                            self.pb.chamber_models[ch]['activation_curve'])(
                                self.pb.t_init))
                if self.pb.chamber_models[ch]['type'] == '0D_elast_prescr':
                    self.pb.y.append(
                        self.pb.ti.timecurves(
                            self.pb.chamber_models[ch]['elastance_curve'])(
                                self.pb.t_init))
                if self.pb.chamber_models[ch]['type'] == '0D_prescr':
                    self.pb.c.append(
                        self.pb.ti.timecurves(
                            self.pb.chamber_models[ch]['prescribed_curve'])(
                                self.pb.t_init))

        self.pb.cardvasc0D.evaluate(self.pb.s_old, self.pb.t_init,
                                    self.pb.df_old, self.pb.f_old, None, None,
                                    self.pb.c, self.pb.y, self.pb.aux_old)

        # flow 0d 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.dt

            # offset time for multiple cardiac cycles
            t_off = (
                self.pb.ti.cycle[0] - 1
            ) * self.pb.cardvasc0D.T_cycl  # zero if T_cycl variable is not specified

            # external volume/flux from time curve
            if self.pb.excitation_curve is not None:
                self.pb.c[0] = self.pb.ti.timecurves(
                    self.pb.excitation_curve)(t - t_off)
            # activation curves
            self.pb.evaluate_activation(t - t_off)

            # solve
            self.solnln.newton(self.pb.s, t - t_off)

            # get midpoint dof values for post-processing (has to be called before update!)
            self.pb.cardvasc0D.midpoint_avg(
                self.pb.s, self.pb.s_old, self.pb.s_mid,
                self.pb.theta_ost), self.pb.cardvasc0D.midpoint_avg(
                    self.pb.aux, self.pb.aux_old, self.pb.aux_mid,
                    self.pb.theta_ost)

            # raw txt file output of 0D model quantities
            if self.pb.write_results_every_0D > 0 and N % self.pb.write_results_every_0D == 0:
                self.pb.cardvasc0D.write_output(self.pb.output_path_0D, t,
                                                self.pb.s_mid, self.pb.aux_mid,
                                                self.pb.simname)

            # update timestep
            self.pb.cardvasc0D.update(self.pb.s, self.pb.df, self.pb.f,
                                      self.pb.s_old, self.pb.df_old,
                                      self.pb.f_old, self.pb.aux,
                                      self.pb.aux_old)

            # print to screen
            self.pb.cardvasc0D.print_to_screen(self.pb.s_mid, self.pb.aux_mid)

            # solve time for time step
            wte = time.time()
            wt = wte - wts

            # print time step info to screen
            self.pb.ti.print_timestep(N, t, self.pb.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.cardvasc0D.cycle_check(
                self.pb.s,
                self.pb.sTc,
                self.pb.sTc_old,
                t - t_off,
                self.pb.ti.cycle,
                self.pb.ti.cycleerror,
                self.pb.eps_periodic,
                check=self.pb.periodic_checktype,
                inioutpath=self.pb.output_path_0D,
                nm=self.pb.simname,
                induce_pert_after_cycl=self.pb.perturb_after_cylce)

            # induce some disease/perturbation for cardiac cycle (i.e. valve stenosis or leakage)
            if self.pb.perturb_type is not None and not self.pb.have_induced_pert:
                self.pb.induce_perturbation()

            # write 0D restart info - old and new quantities are the same at this stage (except cycle values sTc)
            if self.pb.write_restart_every > 0 and N % self.pb.write_restart_every == 0:
                self.pb.writerestart(self.pb.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.ti.cycle[0] - 1, self.pb.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()
Ejemplo n.º 6
0
    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()