コード例 #1
0
    def __init__(self, V, two_way=True):
        t0 = time()
        # Keep sub dimensions
        self.ns = V.sub(0).dim()
        self.nf = V.sub(1).dim()
        self.np = V.sub(2).dim()

        # Also dofmaps
        self.dofmap_s = V.sub(0).dofmap().dofs()
        self.dofmap_f = V.sub(1).dofmap().dofs()
        self.dofmap_p = V.sub(2).dofmap().dofs()
        self.dofmap_fp = sorted(self.dofmap_f + self.dofmap_p)
        # Note that f and p dofmaps are used for the fieldsplit Preconditioner
        # in the 2-way splittings, so they are still useful but bear a different meaning.

        # All gather the global fp dofmap
        if two_way:
            comm = MPI.COMM_WORLD
            dofs_fp_global = self.dofmap_fp.copy()
            dofs_fp_global = comm.allgather(dofs_fp_global)
            dofs_fp_global = np.array(tuple(
                chain(*dofs_fp_global)))  # Concat and sort

            # Replace global dofmaps with f-p dofmaps.
            self.dofmap_f, self.dofmap_p = get_local_fp_dofs(
                dofs_fp_global, self.dofmap_f, self.dofmap_p)

        # and Index Sets
        self.is_s = PETSc.IS().createGeneral(self.dofmap_s)
        self.is_f = PETSc.IS().createGeneral(self.dofmap_f)
        self.is_p = PETSc.IS().createGeneral(self.dofmap_p)
        self.is_fp = PETSc.IS().createGeneral(self.dofmap_fp)
        parprint(
            "---- [Indexes] computed local indices in {:.3f}s".format(time() -
                                                                      t0))
コード例 #2
0
    def solve(self):
        """
        Solve poromechanics problem. Problem loads are assumed to give a function when evaluated at a specific time. For example:
        f_vol = lambda t: Constant((1,1))
        """

        # All functions start as 0 (for now), so no modifications are required.
        t0_simulation = time()

        if self.output_solutions:
            self.export(self.t0)

        current_time = time()
        iterations = []
        while self.t < self.tf:

            self.t += self.dt
            its = self.solve_time_step(self.t)
            parprint("-------- Solved time t={:.2f}. {} iterations in {:.2f}s".
                     format(self.t, its,
                            time() - current_time))
            if self.output_solutions:
                self.export(self.t)
            current_time = time()
        parprint("Total simulation time = {}s\n".format(time() -
                                                        t0_simulation))
コード例 #3
0
    def __init__(self, parameters, mesh, parser):
        super().__init__(parameters, mesh, parser)
        V = df.FunctionSpace(
            self.mesh,
            df.MixedElement(
                df.VectorElement('CG', self.mesh.ufl_cell(),
                                 parameters["fe degree solid"]),
                df.VectorElement('CG', self.mesh.ufl_cell(),
                                 parameters["fe degree fluid"]),
                df.FiniteElement('CG', self.mesh.ufl_cell(),
                                 parameters["fe degree pressure"])))
        self.V = V
        self.two_way = True
        self.three_way = False
        if "3-way" in self.parameters["pc type"]:
            self.two_way = False
            self.three_way = True

        parprint("---- Problem dofs={}, h={}, solving with {} procs".format(
            V.dim(), mesh.hmin(), MPI.COMM_WORLD.size))
        self.assembler = PoromechanicsAssembler(parameters, V, self.three_way)

        self.index_map = IndexSet(V, self.two_way)
        # Start by assembling system matrices
        self.assembler.assemble()

        self.sol = df.Function(V)
        self.us_nm1, self.uf_nm1, self.p_nm1 = self.sol.split(True)
        self.us_nm2 = self.us_nm1.copy(True)

        self.first_timestep = True
コード例 #4
0
    def create_solver(self, A, b, PC):
        t0_create = time()

        b = self.b.vec()
        self.dummy = b.copy()
        self.dummy_s = PETSc.Vec().create()
        self.dummy_f = PETSc.Vec().create()
        self.dummy_p = PETSc.Vec().create()
        b.getSubVector(self.index_map.is_s, self.dummy_s)
        b.getSubVector(self.index_map.is_f, self.dummy_f)
        b.getSubVector(self.index_map.is_p, self.dummy_p)
        b.restoreSubVector(self.index_map.is_s, self.dummy_s)
        b.restoreSubVector(self.index_map.is_f, self.dummy_f)
        b.restoreSubVector(self.index_map.is_p, self.dummy_p)

        solver_type = self.parameters["solver type"]
        atol = self.parameters["solver atol"]
        rtol = self.parameters["solver rtol"]
        maxiter = self.parameters["solver maxiter"]
        monitor_convergence = self.parameters["solver monitor"]
        if self.parameters["solver type"] == "aar":
            order = self.parameters["AAR order"]
            p = self.parameters["AAR p"]
            omega = self.parameters["AAR omega"]
            beta = self.parameters["AAR beta"]
            self.solver = AAR(order,
                              p,
                              omega,
                              beta,
                              self.A.mat(),
                              x0=None,
                              pc=self.PC,
                              atol=atol,
                              rtol=rtol,
                              maxiter=maxiter,
                              monitor_convergence=monitor_convergence)
        else:
            solver = PETSc.KSP().create()
            solver.setOptionsPrefix("global_")
            # solver.setInitialGuessNonzero(True)
            solver.setOperators(self.A.mat())
            solver.setType(self.parameters["solver type"])
            solver.setTolerances(rtol, atol, 1e20, maxiter)
            solver.setPC(self.PC)
            if solver_type == "gmres":
                solver.setGMRESRestart(maxiter)
            solver.setFromOptions()
            self.solver = solver
        parprint("---- [Solver] Solver created in {}s".format(time() -
                                                              t0_create))
コード例 #5
0
 def print_timings(self):
     parprint("\n===== Timing preconditioner: {:.3f}s".format(self.t_total))
     if self.flag_3_way:
         parprint("\tSolid solver: {:.3f}s\n\tFluid solver: {:.3f}s\n\tPressure solver: {:.3f}s".format(
             self.t_solid, self.t_fluid, self.t_press))
     else:
         parprint(
             "\tSolid solver: {:.3f}s\n\tFluid-pressure solver: {:.3f}s".format(self.t_solid, self.t_fluid))
     parprint("\n\tAllocation time: {:.3f}".format(self.t_alloc))
コード例 #6
0
    def set_up(self):
        t0_setup = time()
        # Create linear solver
        solver_type = self.parameters["solver type"]
        atol = self.parameters["solver atol"]
        rtol = self.parameters["solver rtol"]
        maxiter = self.parameters["solver maxiter"]
        monitor_convergence = self.parameters["solver monitor"]

        # Prepare elements for convergence test:
        args = None
        # index_map, b, dummy, dummy_s, dummy_f, dummy_p, b0_s, b0_f, b0_p
        b = self.b.vec()
        dummy = b.copy()
        b_s = PETSc.Vec().create()
        b_f = PETSc.Vec().create()
        b_p = PETSc.Vec().create()
        b.getSubVector(self.index_map.is_s, b_s)
        dummy_s = b_s.copy()
        b.getSubVector(self.index_map.is_f, b_f)
        dummy_f = b_f.copy()
        b.getSubVector(self.index_map.is_p, b_p)
        dummy_p = b_p.copy()
        b0_s = b_s.norm()
        b0_f = b_f.norm()
        b0_p = b_p.norm()
        b.restoreSubVector(self.index_map.is_s, b_s)
        b.restoreSubVector(self.index_map.is_f, b_f)
        b.restoreSubVector(self.index_map.is_p, b_p)
        # if b0_s < 1e-13:
        #     b0_s = 1
        # if b0_f < 1e-13:
        #     b0_f = 1
        # if b0_p < 1e-13:
        #     b0_p = 1
        kwargs = {
            'index_map': self.index_map,
            'b': b,
            'dummy': dummy,
            'dummy_subs': (dummy_s, dummy_f, dummy_p),
            'b0_norms': (b0_s, b0_f, b0_p),
            'monitor': monitor_convergence
        }

        parprint("---- [Solver] Solver set up in {}s".format(time() -
                                                             t0_setup))
コード例 #7
0
    def setUp(self, pc):
        t0_setup = time()
        # create local ksp and pc contexts
        self.create_solvers()

        # Create temp block vectors used in apply()
        self.allocate_temp_vectors()

        # Extract sub-matrices
        self.allocate_submatrices()

        for solver, mat in zip(self.ksps_elliptic, self.matrices_elliptic):
            self.setup_elliptic_solver(solver, mat)

        if not self.flag_3_way:
            if self.inner_pc_type == "lu":
                self.setup_elliptic_solver(self.ksp_fp, self.Mfp_fp)
            else:
                self.setup_fieldsplit(self.ksp_fp, self.Mfp_fp)
        parprint("---- [Preconditioner] Set up in {}s".format(time() - t0_setup))
コード例 #8
0
    def set_bcs(self, bcs, bcs_diff):
        """
        Set boundary conditions to both physics. Assumed to be constant.
        """
        t0 = time()
        self.bcs = bcs
        self.bcs_diff = bcs_diff

        # Create map for pressure dofs, used in 3way CC preconditioner
        dofs_p = self.V.sub(2).dofmap().dofs()
        bcs_sub_pressure = []
        for b in bcs_diff:
            bc_vals = b.get_boundary_values().keys()
            for i, dof in enumerate(dofs_p):
                if dof in bc_vals:
                    bcs_sub_pressure.append(i)
        self.bcs_sub_pressure = bcs_sub_pressure
        parprint(
            "---- [BC] Created inverse pressure BC in {:.3f}s".format(time() -
                                                                      t0))
コード例 #9
0
    def getRHS(self, t, us_nm1, us_nm2, uf_nm1, p_nm1):
        """
        Get Dolfin::PETScVector RHS at time t
        """
        t0_rhs = time()
        v, w, q = TestFunctions(self.V)

        # Compute solid residual
        rhs_s_n = dot(self.fs_sur(t), v) * self.dsNs + self.phis * \
            self.rhos * dot(self.fs_vol(t), v) * dx
        lhs_s_n = dot(self.rhos * self.idt**2 * self.phis * (-2. * us_nm1 + us_nm2), v) * \
            dx - self.phi0**2 * dot(self.ikf * (- self.idt * (- us_nm1)), v) * dx
        r_s = rhs_s_n - lhs_s_n

        # Compute fluid residual
        rhs_f_n = dot(self.ff_sur(t), w) * self.dsNf + self.phi0 * \
            self.rhof * dot(self.ff_vol(t), w) * dx
        lhs_f = dot(self.rhof * self.idt * self.phi0 * (- uf_nm1), w) + self.phi0**2 * \
            dot(self.ikf * (-self.idt * (-us_nm1)), w)
        lhs_f_n = lhs_f * dx

        r_f = rhs_f_n - lhs_f_n

        # Compute pressure residual
        rhs_p_n = 1 / self.rhof * self.p_source(t) * q * dx

        D_sf = div(self.idt * self.phis * (-us_nm1)) * q
        M_p = self.phis**2 / Constant(self.ks * self.dt) * (-p_nm1) * q
        lhs_p_n = (M_p + D_sf) * dx
        r_p = rhs_p_n - lhs_p_n

        # assemble(rhs_s_n + rhs_f_n + rhs_p_n, tensor=self.b)
        assemble(self.adim_s * rhs_s_n + self.adim_f * rhs_f_n +
                 self.adim_p * rhs_p_n,
                 tensor=self.b)
        parprint("---- [Assembler] Assembly RHS = {}s".format(time() - t0_rhs))
        return self.b
コード例 #10
0
def converged(_ksp, _it, _rnorm, *args, **kwargs):
    """
    args must have: index_map, dummy, dummy_s, dummy_f, dummy_p, b0_s, b0_f, b0_p.
    dummy is used to avoid allocation of new vector for residual. [is is somewhere in PETSc...?]
    """
    dummy = kwargs['dummy']
    index_map = kwargs['index_map']
    dummy_s, dummy_f, dummy_p = kwargs['dummy_subs']
    b0_s, b0_f, b0_p = kwargs['b0_norms']
    normalize = max(b0_s, b0_f, b0_p)
    _ksp.buildResidual(dummy)

    # Get residual subcomponents
    dummy.getSubVector(index_map.is_s, dummy_s)
    dummy.getSubVector(index_map.is_f, dummy_f)
    dummy.getSubVector(index_map.is_p, dummy_p)

    res_s_a = dummy_s.norm(PETSc.NormType.NORM_INFINITY)
    res_s_r = res_s_a / normalize  # /b0_s
    res_f_a = dummy_f.norm(PETSc.NormType.NORM_INFINITY)
    res_f_r = res_f_a / normalize  # /b0_f
    res_p_a = dummy_p.norm(PETSc.NormType.NORM_INFINITY)
    res_p_r = res_p_a / normalize  # /b0_p

    error_abs = max(res_s_a, res_f_a, res_p_a)
    error_rel = max(res_s_r, res_f_r, res_p_r)
    if kwargs['monitor']:
        width = 11
        if _it == 0:
            parprint("KSP errors: {}, {}, {}, {}, {}, {}".format(
                'abs_s'.rjust(width), 'abs_f'.rjust(width),
                'abs_p'.rjust(width), 'rel_s'.rjust(width),
                'rel_f'.rjust(width), 'rel_p'.rjust(width)))

        parprint("KSP it {}:   {:.5e}, {:.5e}, {:.5e}, {:.5e}, {:.5e}, {:.5e}".
                 format(_it, res_s_a, res_f_a, res_p_a, res_s_r, res_f_r,
                        res_p_r))
    if error_abs < _ksp.atol or error_rel < _ksp.rtol:
        # Convergence
        parprint("---- [Solver] Converged")
        return 1
    elif _it > _ksp.max_it or error_abs > _ksp.divtol:
        # Divergence
        return -1
    else:
        # Continue
        return 0
コード例 #11
0
 def print_timings(self):
     parprint("\n===== Timing Solver: {:.3f}s".format(self.t_total))
コード例 #12
0
phi0 = 0.1
mu_s = 4000
lmbda = 700
ks = 1e6
kf = 1e-7
dt = 0.1
t0 = 0.0
tf = 0.1
maxiter = 1000
betas = -0.5
betaf = 0.
betap = 1.

# FE space
V = FunctionSpace(mesh, VectorElement('CG', tetrahedron, degree_s))
parprint("Dofs = {}".format(V.dim()))
sol = Function(V)
us_nm1 = Function(V)
us_nm2 = Function(V)

# BCs
bcs = [
    DirichletBC(V.sub(0), Constant(0), markers, XM),
    DirichletBC(V.sub(1), Constant(0), markers, YM),
    DirichletBC(V.sub(2), Constant(0), markers, ZM)
]


def fs_sur(t):
    return Constant(-1e3 * 0.9 * (1 - exp(-(t**2) / 0.25))) * FacetNormal(mesh)
コード例 #13
0
    def assemble(self):
        t0_assemble = time()

        def hooke(ten):
            return 2 * self.mu_s * ten + self.lmbda * tr(ten) * Identity(
                self.dim)

        def eps(vec):
            return sym(grad(vec))

        us, vf, p = TrialFunctions(self.V)
        v, w, q = TestFunctions(self.V)
        dx = Measure('dx', domain=self.mesh)

        # First base matrix
        a_s = (self.rhos * self.idt**2 * self.phis * dot(us, v) +
               inner(hooke(eps(us)), eps(v)) - p * div(self.phis * v) -
               self.phi0**2 * dot(self.ikf * (vf - self.idt * us), v)) * dx

        a_f = (self.rhof * self.idt * self.phi0 * dot(vf, w) + 2. * self.mu_f *
               inner(self.phi0 * eps(vf), eps(w)) - p * div(self.phi0 * w) +
               self.phi0**2 * dot(self.ikf * (vf - self.idt * us), w)) * dx

        a_p = (self.phis**2 * self.idt / self.ks * p * q +
               div(self.phi0 * vf) * q +
               div(self.phis * self.idt * us) * q) * dx

        a_p_diff = Constant(0.0) * p * q * dx

        assemble(self.adim_s * a_s + self.adim_f * a_f + self.adim_p * a_p,
                 tensor=self.A)

        # Then, preconditioner matrices (FS and DIFF)
        if self.prec_type == "undrained":
            N = self.ks / self.phis**2

            a_s = (self.rhos * self.idt**2 * self.phis * dot(us, v) +
                   inner(hooke(eps(us)), eps(v)) +
                   N * div(self.phis * us) * div(self.phis * v) -
                   self.phi0**2 * dot(self.ikf * (-self.idt * us), v)) * dx

            a_f = (self.rhof * self.idt * self.phi0 * dot(vf, w) +
                   2. * self.mu_f * inner(self.phi0 * eps(vf), eps(w)) -
                   p * div(self.phi0 * w) +
                   self.phi0**2 * dot(self.ikf * (vf - self.idt * us), w)) * dx

            a_p = (self.phis**2 * self.idt / self.ks * p * q +
                   div(self.phi0 * vf) * q +
                   div(self.phis * self.idt * us) * q) * dx
            a_p_diff = 0 * q * dx
        elif self.prec_type == "undrained 3-way":
            N = self.ks / self.phis**2

            a_s = (self.rhos * self.idt**2 * self.phis * dot(us, v) +
                   inner(hooke(eps(us)), eps(v)) +
                   N * div(self.phis * us) * div(self.phis * v) -
                   self.phi0**2 * dot(self.ikf * (-self.idt * us), v)) * dx

            a_f = (self.rhof * self.idt * self.phi0 * dot(vf, w) +
                   2. * self.mu_f * inner(self.phi0 * eps(vf), eps(w)) -
                   p * div(self.phi0 * w) +
                   self.phi0**2 * dot(self.ikf * (vf - self.idt * us), w)) * dx
            beta_CC1 = self.phi0 / Constant(2. * self.mu_f / self.dim)
            beta_CC2 = inv(self.rhof * self.idt / self.phi0 + self.ikf)

            a_p = (self.phis**2 * self.idt / self.ks * p * q +
                   beta_CC1 * p * q) * dx

            a_p_diff = (self.phis**2 * self.idt / self.ks * p * q +
                        dot(beta_CC2 * grad(p), grad(q))) * dx
        elif self.prec_type == "diagonal":
            beta_s_hat = self.betas

            a_s = (self.rhos * self.idt**2 * self.phis * dot(us, v) +
                   inner(hooke(eps(us)), eps(v)) - p * div(self.phis * v) -
                   self.phi0**2 *
                   dot(self.ikf * (vf -
                                   (1. + beta_s_hat) * self.idt * us), v)) * dx

            beta_f_hat = self.betaf

            a_f = (
                self.rhof * self.idt * self.phi0 * dot(vf, w) +
                2. * self.mu_f * inner(self.phi0 * eps(vf), eps(w)) -
                p * div(self.phi0 * w) +
                (1. + beta_f_hat) * self.phi0**2 * dot(self.ikf * vf, w)) * dx

            beta_p_hat = self.betap
            beta_p = beta_p_hat * self.phis**2 / \
                (self.dt * (2. * self.mu_s / self.dim + self.lmbda))

            a_p = (self.phis**2 * self.idt / self.ks * p * q + beta_p * p * q +
                   div(self.phi0 * vf) * q) * dx
            a_p_diff = 0.0 * q * dx
        elif self.prec_type == "diagonal 3-way":
            beta_s_hat = self.betas

            a_s = (self.rhos * self.idt**2 * self.phis * dot(us, v) +
                   inner(hooke(eps(us)), eps(v)) - p * div(self.phis * v) -
                   self.phi0**2 *
                   dot(self.ikf * (vf -
                                   (1. + beta_s_hat) * self.idt * us), v)) * dx

            beta_f_hat = self.betaf

            a_f = (self.rhof * self.idt * self.phi0 * dot(vf, w) +
                   2. * self.mu_f * self.phi0 * inner(eps(vf), eps(w)) -
                   p * div(self.phi0 * w) +
                   (1. + beta_f_hat) * self.phi0**2 * dot(self.ikf *
                                                          (vf), w)) * dx

            beta_p_hat = self.betap
            beta_p = beta_p_hat * self.phis**2 / \
                Constant(self.dt * (2. * self.mu_s / self.dim + self.lmbda))
            beta_CC1 = self.phi0 / Constant(2. * self.mu_f / self.dim)
            beta_CC2 = inv(self.rhof * self.idt / self.phi0 + self.ikf)

            a_p = (self.phis**2 * self.idt / self.ks * p * q +
                   (beta_p + beta_CC1) * p * q) * dx

            a_p_diff = (self.phis**2 * self.idt / self.ks * p * q +
                        beta_p * p * q + dot(beta_CC2 * grad(p), grad(q))) * dx
        elif self.prec_type == "diagonal 3-way-II":
            beta_s_hat = self.betas

            a_s = (self.rhos * self.idt**2 * self.phis * dot(us, v) +
                   inner(hooke(eps(us)), eps(v)) - p * div(self.phis * v) -
                   self.phi0**2 *
                   dot(self.ikf * (vf -
                                   (1. + beta_s_hat) * self.idt * us), v)) * dx

            beta_f_hat = self.betaf
            beta_p_hat = self.betap
            beta_p = beta_p_hat * self.phis**2 / \
                (self.dt * (2. * self.mu_s / self.dim + self.lmbda))

            a_f = (
                self.rhof * self.idt * self.phi0 * dot(vf, w) +
                2. * self.mu_f * inner(self.phi0 * eps(vf), eps(w)) + 1. /
                (self.phis**2 * self.idt / self.ks + beta_p) *
                div(self.phi0 * vf) * div(self.phi0 * w) +
                (1. + beta_f_hat) * self.phi0**2 * dot(self.ikf * vf, w)) * dx

            a_p = (self.phis**2 * self.idt / self.ks * p * q + beta_p * p * q +
                   div(self.phi0 * vf) * q) * dx
            a_p_diff = 0.0 * q * dx
        else:
            a_s = a_s
            a_f = a_f
            a_p = a_p
        assemble(self.adim_s * a_s + self.adim_f * a_f + self.adim_p * a_p,
                 tensor=self.P)
        if self.three_way:
            assemble(self.adim_s * a_s + self.adim_f * a_f +
                     self.adim_p * a_p_diff,
                     tensor=self.P_diff)

        parprint(
            "---- [Assembler] Assembly A, P time = {}s".format(time() -
                                                               t0_assemble))
コード例 #14
0
    def solve(self, b, sol):

        if not self.x0:
            self.x0 = b.copy()
            self.x0.zeroEntries()

        # Initialize current vectors
        self.temp_vec = None
        self.xk = self.x0.copy()
        self.fk = b.copy()
        self.fk.axpy(-1, self.matA * self.x0)
        self.delta_xk = b.copy()
        self.delta_fk = b.copy()
        self.temp_vec = b.copy()
        # Init global vectors and scatterer
        self.scatter, aux = PETSc.Scatter.toZero(b)
        self.d_fk0 = aux.copy()
        self.d_fk0.zeroEntries()
        self.fk0 = aux.copy()
        self.fk0.zeroEntries()

        error0 = self.fk.norm()
        err_abs = error0
        err_rel = 1
        it = 0
        alpha = None
        current_type = ""
        while err_abs > self.atol and err_rel > self.rtol and it < self.maxiter:

            self.fk.copy(self.delta_fk)
            self.xk.copy(self.delta_xk)
            self.update_residual(b)  # Update fk value
            self.delta_fk.aypx(-1, self.fk)

            self.F.append(self.delta_fk.copy())
            if len(self.F) > self.order:
                self.F.pop(0)

            # Update global vectors on first cpu
            self.scatter.scatter(self.delta_fk, self.d_fk0)
            self.scatter.scatter(self.fk, self.fk0)
            self.F0.append(self.d_fk0.copy())
            if len(self.F0) > self.order:
                self.F0.pop(0)

            if self.fk.norm() < 1e-14:
                pass
            # If not a natural number or first iteration
            elif it == 0 or self.order == 0 or (it + 1) / self.p % 1 > 0:
                current_type = "R"
                self.xk.axpy(self.omega, self.fk)
            else:
                current_type = "A"
                mk = min(self.order, it)
                # Process only on first core, then scatter alpha
                if self.rank == 0:
                    F = np.vstack(self.F0).T
                    Q, R = np.linalg.qr(F)
                    rhs = self.fk0
                    alpha = np.linalg.solve(R, -Q.T @ rhs)
                else:
                    alpha = None
                alpha = self.comm.bcast(alpha, root=0)
                self.xk.axpy(self.beta, self.fk)
                for i in range(mk):
                    self.xk.axpy(alpha[i], self.X[i] + self.beta * self.F[i])

            self.delta_xk.aypx(-1, self.xk)
            self.X.append(self.delta_xk.copy())
            if len(self.X) > self.order:
                self.X.pop(0)
            err_abs = self.fk.norm()
            err_rel = err_abs / error0
            it += 1

            if self.monitor_convergence:
                parprint("---- Iteration [{}] {:3}\tabs={:1.2e}\trel={:1.2e}".
                         format(current_type, it, err_abs, err_rel))

        # Update solution and return number of iterations
        self.xk.copy(sol)
        self.it = it
        return it