def __init__(self, problem, solver_params_solid, solver_params_flow0d):

        self.pb = problem

        # initialize solver instances
        self.solversmall = SolidmechanicsFlow0DSolver(self.pb.pbsmall,
                                                      solver_params_solid,
                                                      solver_params_flow0d)
        self.solverlarge = SolidmechanicsSolver(self.pb.pblarge,
                                                solver_params_solid)

        # read restart information
        if self.pb.restart_cycle > 0:
            self.pb.pbsmall.pbs.simname = self.pb.simname_small + str(
                self.pb.restart_cycle)
            self.pb.pblarge.simname = self.pb.simname_large + str(
                self.pb.restart_cycle)
            self.pb.pbsmall.pbs.io.readcheckpoint(self.pb.pbsmall.pbs,
                                                  self.pb.restart_cycle)
            self.pb.pblarge.io.readcheckpoint(self.pb.pblarge,
                                              self.pb.restart_cycle)
            self.pb.pbsmall.pbf.readrestart(self.pb.pbsmall.pbs.simname,
                                            self.pb.restart_cycle)
            # no need to do after restart
            self.pb.pbsmall.pbs.prestress_initial = False
            # induce the perturbation
            self.pb.pbsmall.induce_perturbation()
            # next small scale run is a resumption of a previous one
            self.pb.pbsmall.restart_multiscale = True
Ejemplo n.º 2
0
    def __init__(self, problem, solver_params_solid, solver_params_flow0d):

        self.pb = problem

        self.solver_params_solid = solver_params_solid
        self.solver_params_flow0d = solver_params_flow0d

        # initialize nonlinear solver class
        self.solnln = solver_nonlin.solver_nonlinear_constraint_monolithic(
            self.pb, self.pb.pbs.V_u, self.pb.pbs.V_p,
            self.solver_params_solid, self.solver_params_flow0d)

        if self.pb.pbs.prestress_initial:
            # add coupling work to prestress weak form
            self.pb.pbs.weakform_prestress_u -= self.pb.work_coupling_prestr
            # initialize solid mechanics solver
            self.solverprestr = SolidmechanicsSolver(self.pb.pbs,
                                                     self.solver_params_solid)
Ejemplo n.º 3
0
class SolidmechanicsConstraintSolver():
    def __init__(self, problem, solver_params_solid, solver_params_constr):

        self.pb = problem

        self.solver_params_solid = solver_params_solid
        self.solver_params_constr = solver_params_constr

        self.solve_type = self.solver_params_solid['solve_type']

        # initialize nonlinear solver class
        self.solnln = solver_nonlin.solver_nonlinear_constraint_monolithic(
            self.pb, self.pb.pbs.V_u, self.pb.pbs.V_p,
            self.solver_params_solid, self.solver_params_constr)

        if self.pb.pbs.prestress_initial:
            # add coupling work to prestress weak form
            self.pb.pbs.weakform_prestress_u -= self.pb.work_coupling_prestr
            # initialize solid mechanics solver
            self.solverprestr = SolidmechanicsSolver(self.pb.pbs,
                                                     self.solver_params_solid)

    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()
class SolidmechanicsFlow0DMultiscaleGrowthRemodelingSolver():
    def __init__(self, problem, solver_params_solid, solver_params_flow0d):

        self.pb = problem

        # initialize solver instances
        self.solversmall = SolidmechanicsFlow0DSolver(self.pb.pbsmall,
                                                      solver_params_solid,
                                                      solver_params_flow0d)
        self.solverlarge = SolidmechanicsSolver(self.pb.pblarge,
                                                solver_params_solid)

        # read restart information
        if self.pb.restart_cycle > 0:
            self.pb.pbsmall.pbs.simname = self.pb.simname_small + str(
                self.pb.restart_cycle)
            self.pb.pblarge.simname = self.pb.simname_large + str(
                self.pb.restart_cycle)
            self.pb.pbsmall.pbs.io.readcheckpoint(self.pb.pbsmall.pbs,
                                                  self.pb.restart_cycle)
            self.pb.pblarge.io.readcheckpoint(self.pb.pblarge,
                                              self.pb.restart_cycle)
            self.pb.pbsmall.pbf.readrestart(self.pb.pbsmall.pbs.simname,
                                            self.pb.restart_cycle)
            # no need to do after restart
            self.pb.pbsmall.pbs.prestress_initial = False
            # induce the perturbation
            self.pb.pbsmall.induce_perturbation()
            # next small scale run is a resumption of a previous one
            self.pb.pbsmall.restart_multiscale = True

    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()

    def set_state_small(self):

        # set delta small to large
        u_delta = PETSc.Vec().createMPI(
            (self.pb.pblarge.u.vector.getLocalSize(),
             self.pb.pblarge.u.vector.getSize()),
            bsize=self.pb.pblarge.u.vector.getBlockSize(),
            comm=self.pb.comm)
        u_delta.waxpy(-1.0, self.pb.pbsmall.pbs.u_set.vector,
                      self.pb.pblarge.u.vector)
        if self.pb.pbsmall.pbs.incompressible_2field:
            p_delta = PETSc.Vec().createMPI(
                (self.pb.pblarge.p.vector.getLocalSize(),
                 self.pb.pblarge.p.vector.getSize()),
                bsize=self.pb.pblarge.p.vector.getBlockSize(),
                comm=self.pb.comm)
            p_delta.waxpy(-1.0, self.pb.pbsmall.pbs.p_set.vector,
                          self.pb.pblarge.p.vector)

        # update small scale variables - add delta from growth to last small scale displacement
        self.pb.pbsmall.pbs.u.vector.axpy(1.0, u_delta)
        self.pb.pbsmall.pbs.u.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
        self.pb.pbsmall.pbs.u_old.vector.axpy(1.0, u_delta)
        self.pb.pbsmall.pbs.u_old.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
        if self.pb.pbsmall.pbs.incompressible_2field:
            self.pb.pbsmall.pbs.p.vector.axpy(1.0, p_delta)
            self.pb.pbsmall.pbs.p.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
            self.pb.pbsmall.pbs.p_old.vector.axpy(1.0, p_delta)
            self.pb.pbsmall.pbs.p_old.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

        #self.pb.pbsmall.pbs.v_old.vector.set(0.0)
        #self.pb.pbsmall.pbs.v_old.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
        #self.pb.pbsmall.pbs.a_old.vector.set(0.0)
        #self.pb.pbsmall.pbs.a_old.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

        # 0D variables s and s_old are already correctly set from the previous small scale run (end values)

        # set constant prescribed growth stretch for subsequent small scale
        self.pb.pbsmall.pbs.theta.vector.axpby(1.0, 0.0,
                                               self.pb.pblarge.theta.vector)
        self.pb.pbsmall.pbs.theta.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
        self.pb.pbsmall.pbs.theta_old.vector.axpby(
            1.0, 0.0, self.pb.pblarge.theta.vector)
        self.pb.pbsmall.pbs.theta_old.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

    def set_state_large(self, N):

        # update large scale variables
        # only needed once - set prestressing history deformation gradient and spring offset from small scale
        if self.pb.prestress_initial and N == 1:
            self.pb.pblarge.F_hist.vector.axpby(
                1.0, 0.0, self.pb.pbsmall.pbs.F_hist.vector)
            self.pb.pblarge.F_hist.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
            self.pb.pblarge.u_pre.vector.axpby(
                1.0, 0.0, self.pb.pbsmall.pbs.u_pre.vector)
            self.pb.pblarge.u_pre.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

        self.pb.pblarge.u_set.vector.axpby(1.0, 0.0,
                                           self.pb.pbsmall.pbs.u_set.vector)
        self.pb.pblarge.u_set.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

        self.pb.pblarge.u.vector.axpby(1.0, 0.0,
                                       self.pb.pbsmall.pbs.u_set.vector)
        self.pb.pblarge.u.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT,
                                             mode=PETSc.ScatterMode.FORWARD)
        if self.pb.pblarge.incompressible_2field:
            self.pb.pblarge.p.vector.axpby(1.0, 0.0,
                                           self.pb.pbsmall.pbs.p_set.vector)
            self.pb.pblarge.p.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

        # constant large scale active tension
        self.pb.pblarge.tau_a.vector.axpby(
            1.0, 0.0, self.pb.pbsmall.pbs.tau_a_set.vector)
        self.pb.pblarge.tau_a.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
        if self.pb.pblarge.have_frank_starling:
            self.pb.pblarge.amp_old.vector.axpby(
                1.0, 0.0, self.pb.pbsmall.pbs.amp_old_set.vector)
            self.pb.pblarge.amp_old.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

        # pressures from growth set point
        self.pb.pbsmall.pbf.cardvasc0D.set_pressure_fem(
            self.pb.pbsmall.pbf.s_set, self.pb.pbsmall.pbf.cardvasc0D.v_ids,
            self.pb.pbsmall.pr0D, self.pb.neumann_funcs)

        # growth thresholds from set point
        self.pb.pblarge.growth_thres.vector.axpby(
            1.0, 0.0, self.pb.pbsmall.pbs.growth_thres.vector)
        self.pb.pblarge.growth_thres.vector.ghostUpdate(
            addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

    def compute_volume_large(self):

        J_all = as_ufl(0)
        for n in range(self.pb.pblarge.num_domains):

            J_all += self.pb.pblarge.ki.J(
                self.pb.pblarge.u) * self.pb.pblarge.dx_[n]

        vol = assemble_scalar(J_all)
        vol = self.pb.comm.allgather(vol)
        volume_large = sum(vol)

        if self.pb.comm.rank == 0:
            print('Volume of myocardium: %.4e' % (volume_large))
            sys.stdout.flush()

        return volume_large
Ejemplo n.º 5
0
class SolidmechanicsFlow0DSolver():
    def __init__(self, problem, solver_params_solid, solver_params_flow0d):

        self.pb = problem

        self.solver_params_solid = solver_params_solid
        self.solver_params_flow0d = solver_params_flow0d

        self.solve_type = self.solver_params_solid['solve_type']

        # initialize nonlinear solver class
        self.solnln = solver_nonlin.solver_nonlinear_constraint_monolithic(
            self.pb, self.pb.pbs.V_u, self.pb.pbs.V_p,
            self.solver_params_solid, self.solver_params_flow0d)

        if self.pb.pbs.prestress_initial:
            # add coupling work to prestress weak form
            self.pb.pbs.weakform_prestress_u -= self.pb.work_coupling_prestr
            # initialize solid mechanics solver
            self.solverprestr = SolidmechanicsSolver(self.pb.pbs,
                                                     self.solver_params_solid)

    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)

        # 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

        if self.pb.coupling_type == 'monolithic_direct':
            # old 3D coupling quantities (volumes or fluxes)
            self.pb.pbf.c = []
            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':
            self.pb.pbf.c, 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)
                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 and not self.pb.restart_multiscale:
            # 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 0D flow 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 + self.pb.t_prev  # t_prev for multiscale analysis (time from previous cycles)

            # 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)

            # 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 - t_off)

            # activation curves for 0D chambers (if present)
            self.pb.pbf.evaluate_activation(t - t_off)

            # solve
            self.solnln.newton(self.pb.pbs.u,
                               self.pb.pbs.p,
                               self.pb.pbf.s,
                               t - t_off,
                               locvar=self.pb.pbs.theta,
                               locresform=self.pb.pbs.r_growth,
                               locincrform=self.pb.pbs.del_theta)

            # 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 - solid and 0D model
            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)
            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)

            if self.pb.have_multiscale_gandr:
                self.set_homeostatic_threshold(t), self.set_growth_trigger(
                    t - t_off)

            # update old pressures on solid
            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()

    # for multiscale G&R analysis
    def set_homeostatic_threshold(self, t):

        # time is absolute time (should only be set in first cycle)
        eps = 1.0e-14
        if t >= self.pb.t_gandr_setpoint - eps and t < self.pb.t_gandr_setpoint + self.pb.pbs.dt - eps:

            if self.pb.comm.rank == 0:
                print('Set homeostatic growth thresholds...')
                sys.stdout.flush()
            time.sleep(1)

            growth_thresolds = []
            for n in range(self.pb.pbs.num_domains):

                if self.pb.pbs.mat_growth[n]:

                    growth_settrig = self.pb.pbs.constitutive_models[
                        'MAT' + str(n + 1) + '']['growth']['growth_settrig']

                    if growth_settrig == 'fibstretch':
                        growth_thresolds.append(self.pb.pbs.ma[n].fibstretch_e(
                            self.pb.pbs.ki.C(self.pb.pbs.u), self.pb.pbs.theta,
                            self.pb.pbs.fib_func[0]))
                    elif growth_settrig == 'volstress':
                        growth_thresolds.append(
                            tr(self.pb.pbs.ma[n].M_e(
                                self.pb.pbs.u,
                                self.pb.pbs.p,
                                self.pb.pbs.ki.C(self.pb.pbs.u),
                                ivar=self.pb.pbs.internalvars)))
                    else:
                        raise NameError(
                            "Unknown growth trigger to be set as homeostatic threshold!"
                        )

                else:

                    growth_thresolds.append(as_ufl(0))

            growth_thres_proj = project(growth_thresolds,
                                        self.pb.pbs.Vd_scalar, self.pb.pbs.dx_)
            self.pb.pbs.growth_thres.vector.ghostUpdate(
                addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE)
            self.pb.pbs.growth_thres.interpolate(growth_thres_proj)

    # for multiscale G&R analysis
    def set_growth_trigger(self, t):

        # time is relative time (w.r.t. heart cycle)
        eps = 1.0e-14
        if t >= self.pb.t_gandr_setpoint - eps and t < self.pb.t_gandr_setpoint + self.pb.pbs.dt - eps:

            if self.pb.comm.rank == 0:
                print('Set growth triggers...')
                sys.stdout.flush()
            time.sleep(1)

            self.pb.pbs.u_set.vector.axpby(1.0, 0.0, self.pb.pbs.u.vector)
            self.pb.pbs.u_set.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)
            if self.pb.pbs.incompressible_2field:
                self.pb.pbs.p_set.vector.axpby(1.0, 0.0, self.pb.pbs.p.vector)
                self.pb.pbs.p_set.vector.ghostUpdate(
                    addv=PETSc.InsertMode.INSERT,
                    mode=PETSc.ScatterMode.FORWARD)

            self.pb.pbs.tau_a_set.vector.axpby(1.0, 0.0,
                                               self.pb.pbs.tau_a.vector)
            self.pb.pbs.tau_a_set.vector.ghostUpdate(
                addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD)

            if self.pb.pbs.have_frank_starling:
                self.pb.pbs.amp_old_set.vector.axpby(
                    1.0, 0.0, self.pb.pbs.amp_old.vector)
                self.pb.pbs.amp_old_set.vector.ghostUpdate(
                    addv=PETSc.InsertMode.INSERT,
                    mode=PETSc.ScatterMode.FORWARD)

            self.pb.pbf.s_set.axpby(1.0, 0.0, self.pb.pbf.s)