Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://github.com/hpc-maths/GenEO
21 May 2024, 17:50:58 UTC
  • Code
  • Branches (7)
  • Releases (0)
  • Visits
    • Branches
    • Releases
    • HEAD
    • refs/github-services/pull/1/head
    • refs/github-services/pull/2/head
    • refs/github-services/pull/3/head
    • refs/heads/PCAWG
    • refs/heads/elasticity-x
    • refs/heads/master
    • refs/heads/matis
    No releases to show
  • 357d68a
  • /
  • GenEO
  • /
  • projection.py
Raw File Download
Take a new snapshot of a software origin

If the archived software origin currently browsed is not synchronized with its upstream version (for instance when new commits have been issued), you can explicitly request Software Heritage to take a new snapshot of it.

Use the form below to proceed. Once a request has been submitted and accepted, it will be processed as soon as possible. You can then check its processing state by visiting this dedicated page.
swh spinner

Processing "take a new snapshot" request ...

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge
swh:1:cnt:56a64f50cb9b97247e7ae6213c703fbbddc2efca
origin badgedirectory badge
swh:1:dir:c718fa075e0f2213f83d035c3769d35d37167ada
origin badgerevision badge
swh:1:rev:76ab96c30d909964ced1b4bc77f09173232ab37e
origin badgesnapshot badge
swh:1:snp:5600c58bc442523ff2d2f69616f3f4483ec0d504

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Tip revision: 76ab96c30d909964ced1b4bc77f09173232ab37e authored by Loic Gouarin on 12 December 2022, 17:12:56 UTC
...
Tip revision: 76ab96c
projection.py
# Authors:
#     Loic Gouarin <loic.gouarin@cmap.polytechnique.fr>
#     Nicole Spillane <nicole.spillane@cmap.polytechnique.fr>
#
# License: BSD 3 clause
from petsc4py import PETSc
import mpi4py.MPI as mpi
import numpy as np
from .bc import bcApplyWest_vec
from slepc4py import SLEPc

class minimal_V0(object):
    def __init__(self,ksp_Atildes,V0s=[],labs=[]):
        """
        Compute local contributions to the minimal coarse space, i.e., the kernel of Atildes. Only implemented if the solver for Atildes is mumps.
        Parameters
        ==========

        PETSc.Options
        =============
        PCBNN_mumpsCntl3 : Real
        Default is 1e-6
        This is a parameter passed to mumps: CNTL(3) is used to determine if a pivot is null when the null pivot detection option is used (which is the case when ICNTL(24) = 1).
        If PCBNN_mumpsCntl3 is too small, part of the kernel of the local matrices may be missing which will deteriorate convergence significantly or even prevent the algorithm from converging. If it is too large, then more vectors than strictly needed may be incorporated into the coarse space. This leads to a larger coarse problem but will accelrate convergence.

        PCBNN_verbose : Bool
            Default is False.
            If True, some information about the preconditioners is printed when the code is executed.

        """
        OptDB = PETSc.Options()
        self.mumpsCntl3 = OptDB.getReal('PCBNN_mumpsCntl3', 1e-6)
        self.verbose =  OptDB.getBool('PCBNN_GenEO_verbose', False)

        self.comm = mpi.COMM_SELF
        #compute the kernel of the self.ksp_Atildes operator and initialize local coarse space with it
        self.ksp_Atildes = ksp_Atildes
        _,Atildes = self.ksp_Atildes.getOperators()
        self.Atildes = Atildes
        works, _ = self.Atildes.getVecs()
        self.works = works
        if self.ksp_Atildes.pc.getFactorSolverType() == 'mumps':
            self.ksp_Atildes.pc.setFactorSetUpSolverType()
            F = self.ksp_Atildes.pc.getFactorMatrix()
            F.setMumpsIcntl(7, 2)
            F.setMumpsIcntl(24, 1)
            F.setMumpsCntl(3, self.mumpsCntl3)
            self.ksp_Atildes.pc.setUp()
            nrb = F.getMumpsInfog(28)

            for i in range(nrb):
                F.setMumpsIcntl(25, i+1)
                works.set(0.)
                self.ksp_Atildes.solve(works, works)
                V0s.append(works.copy())
                labs.append('Ker Atildes')

            F.setMumpsIcntl(25, 0)
            if self.verbose:
                PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors as zero energy modes of local solver'.format(mpi.COMM_WORLD.rank, nrb), comm=self.comm)
        else:
            nrb = 0
        self.V0s = V0s
        self.labs = labs
        self.nrb = nrb
    def view(self):
        print('###')
        print(f'view of minimal_V0 in Subdomain {mpi.COMM_WORLD.rank}')
        if mpi.COMM_WORLD.rank == 0:
            print(f'{self.mumpsCntl3=}')
            print(f'{self.labs=}')
        if (self.ksp_Atildes.pc.getFactorSolverType() == 'mumps'):
            print(f'dim(Ker(Atildes)) = {self.nrb=}')
        else:
            print(f'{self.nrb=}, no kernel computation because pc is not mumps')


class GenEO_V0(object):
    def __init__(self,ksp_Atildes,Ms,As,mult_max,V0s=[],labs=[],ksp_Ms=[]):
        """
        Initialize the coarse space and corresponding projection preconditioner and other coarse operators.
        The default coarse space is the kernel of the local operators if these have been factorized
        with MUMPS and no coarse space otherwise.

        Parameters
        ==========

        PETSc.Options
        =============

        PCBNN_GenEO_eigmin : Real
        Default is 0.1.
        Target for the smallest eigenvalue (eigmin) of the preconditioned operator. This sets the threshold for selecting which eigenvectors from the local generalized eigenvalue problems are selected for the coarse space. There are three cases where we do NOT solve an eigenvalue problem for eigmin:
        - if GenEO = False.
        - if PCBNN_GenEO_eigmin = 0.
        - if PCBNN_switchtoASM = False (this is the case by default) then the preconditioner is BNN and it is already known that eigmin >= 1. In this case the value of PCBNN_GenEO_eigmin is ignored.

        PCBNN_GenEO_eigmax : Real
        Default is 10.
        Target for the largest eigenvalue (eigmax) of the preconditioned operator. This sets the threshold for selecting which eigenvectors from the local generalized eigenvalue problems are selected for the coarse space. There are three cases where we do NOT solve an eigenvalue problem for eigmin:
        - if GenEO = False.
        - if PCBNN_GenEO_eigmax = 0.
        - if PCBNN_switchtoASM = True then the preconditioner is Additive Schwarz and it is already known that eigmax <= maximal multiplicity of a degree of freedom. In this case the value of PCBNN_GenEO_eigmax is ignored.

        PCBNN_GenEO_nev : Int
        Default is 10 .
        Number of eigenpairs requested during the eigensolves. This is an option passed to the eigensolver.

        PCBNN_GenEO_maxev : Int
        Default is 2*PCBNN_GenEO_nev.
        Maximal number of eigenvectors from each eigenvalue problem that can be selected for the coarse space. This is relevant because if more eigenvalues than requested by PBNN_GenEO_nev have converged during the eigensolve then they are all returned so setting the value of PCBNN_GenEO_nev does not impose a limitation on the size of the coarse space.

        PCBNN_mumpsCntl3 : Real
        Default is 1e-6
        This is a parameter passed to mumps: CNTL(3) is used to determine if a pivot is null when the null pivot detection option is used (which is the case when ICNTL(24) = 1).
        If PCBNN_mumpsCntl3 is too small, part of the kernel of the local matrices may be missing which will deteriorate convergence significantly or even prevent the algorithm from converging. If it is too large, then more vectors than strictly needed may be incorporated into the coarse space. This leads to a larger coarse problem but will accelrate convergence.

        PCBNN_verbose : Bool
            Default is False.
            If True, some information about the preconditioners is printed when the code is executed.
        """
        OptDB = PETSc.Options()
        self.tau_eigmax = OptDB.getReal('PCBNN_GenEO_taueigmax', 0.1)
        self.tau_eigmin = OptDB.getReal('PCBNN_GenEO_taueigmin', 0.1)
        self.eigmax = OptDB.getReal('PCBNN_GenEO_eigmax', -1)
        self.eigmin = OptDB.getReal('PCBNN_GenEO_eigmin', -1)
        self.nev = OptDB.getInt('PCBNN_GenEO_nev', 10)
        self.maxev = OptDB.getInt('PCBNN_GenEO_maxev', 2*self.nev)
        self.mumpsCntl3 = OptDB.getReal('PCBNN_mumpsCntl3', 1e-6)
        self.verbose =  OptDB.getBool('PCBNN_GenEO_verbose', False)

        self.comm = mpi.COMM_SELF

        self.As = As
        self.Ms = Ms
        self.mult_max = mult_max

        self.ksp_Atildes = ksp_Atildes
        _,self.Atildes = self.ksp_Atildes.getOperators()

        if ksp_Ms == []:
            self.ksp_Ms = []
        else:
            self.ksp_Ms = ksp_Ms
            _,self.Ms = self.ksp_Ms.getOperators()

        works, _ = self.Ms.getVecs()
        self.works = works

        #thresholds for the eigenvalue problems are computed from the user chosen targets for eigmin and eigmax
        if self.eigmin > 0:
          self.tau_eigmin = self.eigmin

        if self.eigmax > 0:
            self.tau_eigmax = self.mult_max/self.eigmax

        if self.tau_eigmax > 0:
            if self.Atildes != self.As:
                self.solve_GenEO_eigmax(V0s, labs, self.tau_eigmax)
            else:
                self.Lambda_GenEO_eigmax = []
                self.n_GenEO_eigmax = 0
                if self.verbose:
                    PETSc.Sys.Print('This is classical additive Schwarz so eigmax = {} (+1 if fully additive preconditioner), no eigenvalue problem will be solved for eigmax'.format(self.mult_max), comm=mpi.COMM_WORLD)
        else:
            self.Lambda_GenEO_eigmax = []
            self.n_GenEO_eigmax = 0
            if self.verbose:
                PETSc.Sys.Print('Skip GenEO for eigmax as user specified non positive eigmax and taueigmax', comm=mpi.COMM_WORLD)
        if self.tau_eigmin > 0:
            if self.Atildes != self.Ms:
                self.solve_GenEO_eigmin(V0s, labs, self.tau_eigmin)
            else:
                self.Lambda_GenEO_eigmin = []
                self.n_GenEO_eigmin = 0
                self.dimKerMs = []
                if self.verbose:
                    PETSc.Sys.Print('This is BNN so eigmin = 1, no eigenvalue problem will be solved for eigmin', comm=mpi.COMM_WORLD)
        else:
            self.Lambda_GenEO_eigmin = []
            self.n_GenEO_eigmin = 0
            self.dimKerMs = []
            if self.verbose:
                PETSc.Sys.Print('Skip GenEO for eigmin as user specified non positive eigmin and taueigmin', comm=mpi.COMM_WORLD)


        self.V0s = V0s
        self.labs = labs

    def solve_GenEO_eigmax(self, V0s, labs, tauGenEO_eigmax):
        """
        Solves the local GenEO eigenvalue problem related to the largest eigenvalue eigmax.

        Parameters
        ==========

        V0s : list of local PETSc .vecs
            V0s may already contain some local coarse vectors. This routine will possibly add more vectors to the list.

        tauGenEO_eigmax: Real.
            Threshold for selecting eigenvectors for the coarse space.

        """
        if tauGenEO_eigmax > 0:
            eps = SLEPc.EPS().create(comm=PETSc.COMM_SELF)
            eps.setDimensions(nev=self.nev)

            eps.setProblemType(SLEPc.EPS.ProblemType.GHIEP)
            eps.setOperators(self.Atildes , self.As )
            eps.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_REAL)
            eps.setTarget(0.)
            if len(V0s) > 0 :
                eps.setDeflationSpace(V0s)
            ST = eps.getST()
            ST.setType("sinvert")
            ST.setKSP(self.ksp_Atildes)
            eps.solve()
            if eps.getConverged() < self.nev:
                PETSc.Sys.Print('WARNING: Only {} eigenvalues converged for GenEO_eigmax in subdomain {} whereas {} were requested'.format(eps.getConverged(), mpi.COMM_WORLD.rank, self.nev), comm=self.comm)
            if abs(eps.getEigenvalue(eps.getConverged() -1)) < tauGenEO_eigmax:
                PETSc.Sys.Print('WARNING: The largest eigenvalue computed for GenEO_eigmax in subdomain {} is {} < the threshold which is {}. Consider setting PCBNN_GenEO_nev to something larger than {}'.format(mpi.COMM_WORLD.rank, eps.getEigenvalue(eps.getConverged() - 1), tauGenEO_eigmax, eps.getConverged()), comm=self.comm)

            self.Lambda_GenEO_eigmax = []
            self.n_GenEO_eigmax = 0
            for i in range(min(eps.getConverged(),self.maxev)):
                tmp = eps.getEigenvalue(i)
                if(abs(tmp)<tauGenEO_eigmax): #TODO tell slepc that the eigenvalues are real
                    V0s.append(self.works.duplicate())
                    labs.append(f'\lambda_{i}^\sharp = {tmp}')
                    self.Lambda_GenEO_eigmax.append(tmp) #only for viewing
                    eps.getEigenvector(i,V0s[-1])
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamax in subdomain {}: {}'.format(i, mpi.COMM_WORLD.rank, tmp) , comm=self.comm)

                    self.n_GenEO_eigmax += 1
                else:
                    self.Lambda_GenEO_eigmax.append(tmp)  #only for viewing
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamax in subdomain {}: {} <-- not selected (> {})'.format(i, mpi.COMM_WORLD.rank, tmp, tauGenEO_eigmax), comm=self.comm)

            self.eps_eigmax=eps #TODO FIX the only reason for this line is to make sure self.ksp_Atildes and hence PCBNN.ksp is not destroyed
        if self.verbose:
            PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors after first GenEO'.format(mpi.COMM_WORLD.rank, len(V0s)), comm=self.comm)

    def solve_GenEO_eigmin(self, V0s, labs, tauGenEO_eigmin):
        """
        Solves the local GenEO eigenvalue problem related to the smallest eigenvalue eigmin.

        Parameters
        ==========

        V0s : list of local PETSc .vecs
            V0s may already contain some local coarse vectors. This routine will possibly add more vectors to the list.

        tauGenEO_eigmin: Real.
            Threshold for selecting eigenvectors for the coarse space.

        """
        if tauGenEO_eigmin > 0:
            #to compute the smallest eigenvalues of the preconditioned matrix, Ms must be factorized
            if self.ksp_Ms == []:
                tempksp = PETSc.KSP().create(comm=PETSc.COMM_SELF)
                tempksp.setOperators(self.Ms)
                tempksp.setType('preonly')
                temppc = tempksp.getPC()
                temppc.setType('cholesky')
                temppc.setFactorSolverType('mumps')
                temppc.setFactorSetUpSolverType()
                tempF = temppc.getFactorMatrix()
                tempF.setMumpsIcntl(7, 2)
                tempF.setMumpsIcntl(24, 1)
                tempF.setMumpsCntl(3, self.mumpsCntl3)
                temppc.setUp()
                self.dimKerMs = tempF.getMumpsInfog(28)

                for i in range(self.dimKerMs):
                    tempF.setMumpsIcntl(25, i+1)
                    self.works.set(0.)
                    tempksp.solve(self.works, self.works)
                    V0s.append(self.works.copy())
                    labs.append(f'Ker(Ms)')


                tempF.setMumpsIcntl(25, 0)
                if self.verbose:
                    PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors as zero energy modes of Ms (in GenEO for eigmin)'.format(mpi.COMM_WORLD.rank, self.dimKerMs), comm=self.comm)
            else:
                tempksp = self.ksp_Ms
                self.dimKerMs = []
            #Eigenvalue Problem for smallest eigenvalues
            eps = SLEPc.EPS().create(comm=PETSc.COMM_SELF)
            eps.setDimensions(nev=self.nev)

            eps.setProblemType(SLEPc.EPS.ProblemType.GHIEP)
            eps.setOperators(self.Ms,self.Atildes)
            eps.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_REAL)
            eps.setTarget(0.)
            ST = eps.getST()

            ST.setType("sinvert")
            ST.setKSP(tempksp)

            if len(V0s) > 0 :
                eps.setDeflationSpace(V0s)
            eps.solve()
            if eps.getConverged() < self.nev:
                PETSc.Sys.Print('WARNING: Only {} eigenvalues converged for GenEO_eigmin in subdomain {} whereas {} were requested'.format(eps.getConverged(), mpi.COMM_WORLD.rank, self.nev), comm=self.comm)
            self.n_GenEO_eigmin = 0
            self.Lambda_GenEO_eigmin = []
            for i in range(min(eps.getConverged(),self.maxev)):
                tmp = eps.getEigenvalue(i)
                if(abs(tmp)<tauGenEO_eigmin): #TODO tell slepc that the eigenvalues are real
                    V0s.append(self.works.duplicate())
                    labs.append(f'\lambda_{i}^ \ flat = {tmp}')
                    self.Lambda_GenEO_eigmin.append(tmp)
                    eps.getEigenvector(i,V0s[-1])
                    self.n_GenEO_eigmin += 1
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamin in subdomain {}: {}'.format(i, mpi.COMM_WORLD.rank, tmp), comm=self.comm)
                else:
                    self.Lambda_GenEO_eigmin.append(tmp)
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamin in subdomain {}: {} <-- not selected (> {})'.format(i, mpi.COMM_WORLD.rank, eps.getEigenvalue(i), tauGenEO_eigmin), comm=self.comm)
            self.eps_eigmin=eps #the only reason for this line is to make sure self.ksp_Atildes and hence PCBNN.ksp is not destroyed

    def view(self):
        print('###')
        print(f'view of GenEO in Subdomain {mpi.COMM_WORLD.rank}')
        if mpi.COMM_WORLD.rank == 0:
            print(f'{self.tau_eigmax=}')
            print(f'{self.tau_eigmin=}')
            print(f'{self.eigmax=}')
            print(f'{self.eigmin=}')
            print(f'{self.nev=}')
            print(f'{self.maxev=}')
            print(f'{self.mumpsCntl3=}')
            print(f'{self.verbose=}')
            print(f'{self.mult_max=}')
            print(f'Additive Schwarz ? {(self.Atildes == self.As)}')
            print(f'Neumann Neumann ? {(self.Atildes == self.Ms)}')
        print(f'{self.Lambda_GenEO_eigmax=}')
        print(f'{self.n_GenEO_eigmax=}')
        print(f'{self.dimKerMs=}')
        print(f'{self.Lambda_GenEO_eigmin=}')
        print(f'{self.n_GenEO_eigmin=}')
        print(f'{self.labs=}')

class coarse_operators(object):
    def __init__(self,V0s,A,scatter_l2g,works,work,V0_is_global=False):
        """
        V0_is_global is a Boolean that defines whether the coarse space has already been assembled over subdomains. If false, the vectors in V0s are local, will be extended by zero to the whole subdomain to obtain a global coarse space of dimension (sum(len(V0s)). If True then the vectors in V0s are already the local components of some global coarse vectors. The dimension of the global coarse space is len(V0s).
        """
        OptDB = PETSc.Options()
        self.verbose =  OptDB.getBool('PCBNN_GenEO_verbose', False)

        self.comm = mpi.COMM_SELF

        self.scatter_l2g = scatter_l2g
        self.A = A
        self.V0_is_global = V0_is_global
        self.works = works
        self.work = work

        V0, AV0, Delta, ksp_Delta = self.assemble_coarse_operators(V0s)
        #if mpi.COMM_WORLD.rank == 0:
        #    Delta.view()

        self.V0 = V0
        self.AV0 = AV0
        self.Delta = Delta
        self.ksp_Delta = ksp_Delta

        self.gamma = PETSc.Vec().create(comm=PETSc.COMM_SELF)
        self.gamma.setType(PETSc.Vec.Type.SEQ)
        self.gamma.setSizes(len(self.V0))
        self.gamma_tmp = self.gamma.duplicate()

    def assemble_coarse_operators(self,V0s):
        """
        Assembles the coarse operators from a list of local contributions to the coarse space.

        Parameters
        ==========

        V0s : list of local PETSc .vecs
           list of the coarse vectors contributed by the subdomain.

        Returns
        ==========

        V0 : list of local vectors or None ? FIX
            list of the local contributions to the coarse space numbered globally: V0[i] is either a scaled local vector from V0s or None if coarse vector number i belongs to another subdomain. The scaling that is applied to the coarse vectors ensures that their A-norm is 1.

        AV0 : list of global PETSc.Vecs
            list of the A*V0[i]. These are global vectors so not in the same format as the vectors in V0.

        Delta : PETSc.Mat (local)
            matrix of the coarse problem. As a result of the scaling of the coarse vectors, its diagonal is 1. This matrix is duplicated over all subdomains This matrix is duplicated over all subdomains

        ksp_Delta : PETSc.ksp
            Krylov subspace solver for the coarse problem matrix Delta.

        """
        if self.verbose:
            if self.V0_is_global == False:
                PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors in total'.format(mpi.COMM_WORLD.rank, len(V0s)), comm=self.comm)

        if mpi.COMM_WORLD.rank == 0:
            self.gathered_dimV0s = [] #only for view save dims of V0s from each s
        self.work2 = self.work.duplicate()
        if(self.V0_is_global == False):
            V0 = []
            for i in range(mpi.COMM_WORLD.size):
                nrbl = len(V0s) if i == mpi.COMM_WORLD.rank else None
                nrbl = mpi.COMM_WORLD.bcast(nrbl, root=i)
                if mpi.COMM_WORLD.rank == 0:
                    self.gathered_dimV0s.append(nrbl) #only for view save dims of V0s from each s
                for irbm in range(nrbl):
                    V0.append(V0s[irbm].copy() if i == mpi.COMM_WORLD.rank else None)
        else:
            V0 = V0s.copy()

        AV0 = []
        for vec in V0:
            if(self.V0_is_global == False):
                if vec:
                    self.works = vec.copy()
                else:
                    self.works.set(0.)
                self.work.set(0)
                self.scatter_l2g(self.works, self.work, PETSc.InsertMode.ADD_VALUES)
            else:
                self.work = vec.copy()
            #debug1 = np.sqrt(self.work.dot(self.work))
            #debug4 = self.work.norm()
            #debug3 = np.sqrt(self.works.dot(self.works))
            #debug5 = self.works.norm()
            #print(f'normworks {debug3} = {debug5} normwork {debug1} = {debug4}')
            self.A.mult(self.work, self.work2)
            AV0.append(self.work2.copy())
            tmp = np.sqrt(self.work.dot(self.work2))
            if(self.V0_is_global == False):
                self.scatter_l2g(AV0[-1], self.works, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.SCATTER_REVERSE)
                if vec:
                    vec.scale(1./tmp)
                    self.works = vec.copy()
                else:
                    self.works.set(0)
                self.work.set(0)
                self.scatter_l2g(self.works, self.work, PETSc.InsertMode.ADD_VALUES)
                #self.scatter_l2g(self.works, self.work, PETSc.InsertMode.ADD_VALUES)
                #self.A.mult(self.work,self.work2)
                self.A.mult(self.work,AV0[-1])
            else:
                vec.scale(1./tmp)
                self.A.mult(vec,AV0[-1])
            #self.A.mult(self.work,AV0[-1])
            # AV0[-1] = self.work2.copy()
            # AV0[-1] = xtmp.copy()
            #debug6 = np.sqrt(self.work2.dot(self.work2))
            #debug7 = self.work2.norm()
            #debug2 = np.sqrt(AV0[-1].dot(AV0[-1]))
            #debug5 = AV0[-1].norm()
            # debug6 = np.sqrt(self.work2.dot(self.work2))
            # debug7 = self.work2.norm()
            # debug2 = np.sqrt(AV0[-1].dot(AV0[-1]))
            # debug5 = AV0[-1].norm()
#            if mpi.COMM_WORLD.rank == 0:
#                print(f'norm Acoarsevec {debug2} = {debug5}')
            # print(f'norm Acoarsevec {debug2} = {debug5} = {debug6} = {debug7}')


        self.dim = len(V0)
        PETSc.Sys.Print('There are {} vectors in the coarse space.'.format(self.dim), comm=mpi.COMM_WORLD)

        #Define, fill and factorize coarse problem matrix
        Delta = PETSc.Mat().create(comm=PETSc.COMM_SELF)
        Delta.setType(PETSc.Mat.Type.SEQDENSE)
        Delta.setSizes([len(V0),len(V0)])
        Delta.setOption(PETSc.Mat.Option.SYMMETRIC, True)
        Delta.setPreallocationDense(None)
        for i, vec in enumerate(V0):
            if(self.V0_is_global == False):
                if vec:
                    self.works = vec.copy()
                else:
                    self.works.set(0)

                self.work.set(0)
                self.scatter_l2g(self.works, self.work, PETSc.InsertMode.ADD_VALUES)
            else:
                self.work = vec.copy()
            for j in range(i+1):
                tmp = AV0[j].dot(self.work)
                Delta[i, j] = tmp
                Delta[j, i] = tmp
        Delta.assemble()
        ksp_Delta = PETSc.KSP().create(comm=PETSc.COMM_SELF)
        ksp_Delta.setOperators(Delta)
        ksp_Delta.setType('preonly')
        pc = ksp_Delta.getPC()
        pc.setType('cholesky')
        return V0, AV0, Delta, ksp_Delta


    def project(self, x):
        """
        Applies the coarse projection (or projection preconditioner) to x

        Parameters
        ==========

        x : PETSc.Vec
           Vector to which the projection is applied and in which the result is stored.

        """
        alpha = self.gamma.duplicate()
        for i, Avec in enumerate(self.AV0):
            self.gamma[i] = Avec.dot(x)

        self.ksp_Delta.solve(self.gamma, alpha)

        if(self.V0_is_global == False):
            self.works.set(0)
            for i, vec in enumerate(self.V0):
                if vec:
                    self.works.axpy(-alpha[i], vec)

            self.scatter_l2g(self.works, x, PETSc.InsertMode.ADD_VALUES)
        else:
            for i, vec in enumerate(self.V0):
                x.axpy(-alpha[i], vec)

    def coarse_init(self, rhs):
        """
        Initialize the projected PCG algorithm or MPCG algorithm with the solution of the problem in the coarse space.

        Parameters
        ==========

        rhs : PETSc.Vec
           Right hand side vector for which to initialize the problem.

        Returns
        =======

        out : PETSc.Vec
           Solution of the problem projected into the coarse space for the initialization of a projected Krylov subspace solver.

        """

        alpha = self.gamma.duplicate()

        if(self.V0_is_global == False):
            self.scatter_l2g(rhs, self.works, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.SCATTER_REVERSE)
            self.gamma.set(0)
            self.gamma_tmp.set(0)
            for i, vec in enumerate(self.V0):
                if vec:
                    self.gamma_tmp[i] = vec.dot(self.works)

            mpi.COMM_WORLD.Allreduce([self.gamma_tmp, mpi.DOUBLE], [self.gamma, mpi.DOUBLE], mpi.SUM)
        else:
            for i, vec in enumerate(self.V0):
                self.gamma[i] = vec.dot(rhs)

        self.ksp_Delta.solve(self.gamma, alpha)

        if(self.V0_is_global == False):
            self.works.set(0)
            for i, vec in enumerate(self.V0):
                if vec:
                    self.works.axpy(alpha[i], vec)

            out = rhs.duplicate()
            out.set(0)
            self.scatter_l2g(self.works, out, PETSc.InsertMode.ADD_VALUES)
        else:
            out = rhs.duplicate()
            out.set(0)
            for i, vec in enumerate(self.V0):
                    out.axpy(alpha[i], vec)

        return out

    def project_transpose(self, x):
        """
        Applies the transpose of the coarse projection (which is also a projection) to x

        Parameters
        ==========

        x : PETSc.Vec
           Vector to which the projection is applied and in which the result is stored.

        """

        alpha = self.gamma.duplicate()
        if(self.V0_is_global == False):
            self.scatter_l2g(x, self.works, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.SCATTER_REVERSE)
            self.gamma.set(0)
            self.gamma_tmp.set(0)
            for i, vec in enumerate(self.V0):
                if vec:
                    self.gamma_tmp[i] = vec.dot(self.works)

            mpi.COMM_WORLD.Allreduce([self.gamma_tmp, mpi.DOUBLE], [self.gamma, mpi.DOUBLE], mpi.SUM)
        else:
            for i, vec in enumerate(self.V0):
                self.gamma[i] = vec.dot(x)

        self.ksp_Delta.solve(self.gamma, alpha)

        for i in range(len(self.V0)):
            x.axpy(-alpha[i], self.AV0[i])
    def view(self):
        if mpi.COMM_WORLD.rank == 0:
            print('###')
            print(f'view of coarse_operators')# in Subdomain {mpi.COMM_WORLD.rank}')
            print(f'{self.V0_is_global=}')
            print(f'{self.gathered_dimV0s=}')


class projection(object):
    def __init__(self,PCBNN):
        """
        Initialize the coarse space and corresponding projection preconditioner and other coarse operators.
        The default coarse space is the kernel of the local operators if these have been factorized
        with MUMPS and no coarse space otherwise.

        Parameters
        ==========

        PETSc.Options
        =============

        PCBNN_GenEO : Bool
            Default is False.
            If True then the coarse space is enriched by solving local generalized eigenvalue problems.

        PCBNN_GenEO_eigmin : Real
            Default is 0.1.
            Target for the smallest eigenvalue (eigmin) of the preconditioned operator. This sets the threshold for selecting which eigenvectors from the local generalized eigenvalue problems are selected for the coarse space. There are three cases where we do NOT solve an eigenvalue problem for eigmin:
                - if GenEO = False.
                - if PCBNN_GenEO_eigmin = 0.
                - if PCBNN_switchtoASM = False (this is the case by default) then the preconditioner is BNN and it is already known that eigmin >= 1. In this case the value of PCBNN_GenEO_eigmin is ignored.

        PCBNN_GenEO_eigmax : Real
            Default is 10.
            Target for the largest eigenvalue (eigmax) of the preconditioned operator. This sets the threshold for selecting which eigenvectors from the local generalized eigenvalue problems are selected for the coarse space. There are three cases where we do NOT solve an eigenvalue problem for eigmin:
                - if GenEO = False.
                - if PCBNN_GenEO_eigmax = 0.
                - if PCBNN_switchtoASM = True then the preconditioner is Additive Schwarz and it is already known that eigmax <= maximal multiplicity of a degree of freedom. In this case the value of PCBNN_GenEO_eigmax is ignored.

        PCBNN_GenEO_nev : Int
            Default is 10 .
            Number of eigenpairs requested during the eigensolves. This is an option passed to the eigensolver.

        PCBNN_GenEO_maxev : Int
            Default is 2*PCBNN_GenEO_nev.
            Maximal number of eigenvectors from each eigenvalue problem that can be selected for the coarse space. This is relevant because if more eigenvalues than requested by PBNN_GenEO_nev have converged during the eigensolve then they are all returned so setting the value of PCBNN_GenEO_nev does not impose a limitation on the size of the coarse space.

        PCBNN_mumpsCntl3 : Real
            Default is 1e-6
            This is a parameter passed to mumps: CNTL(3) is used to determine if a pivot is null when the null pivot detection option is used (which is the case when ICNTL(24) = 1).
            If PCBNN_mumpsCntl3 is too small, part of the kernel of the local matrices may be missing which will deteriorate convergence significantly or even prevent the algorithm from converging. If it is too large, then more vectors than strictly needed may be incorporated into the coarse space. This leads to a larger coarse problem but will accelrate convergence.
        """
        OptDB = PETSc.Options()
        self.GenEO = OptDB.getBool('PCBNN_GenEO', True)
        self.eigmax = OptDB.getReal('PCBNN_GenEO_eigmax', 10)
        self.eigmin = OptDB.getReal('PCBNN_GenEO_eigmin', 0.1)
        self.nev = OptDB.getInt('PCBNN_GenEO_nev', 10)
        self.maxev = OptDB.getInt('PCBNN_GenEO_maxev', 2*self.nev)
        self.comm = mpi.COMM_SELF
        self.mumpsCntl3 = OptDB.getReal('PCBNN_mumpsCntl3', 1e-6)


        self.scatter_l2g = PCBNN.scatter_l2g
        self.A = PCBNN.A
        self.Ms = PCBNN.Ms
        self.As = PCBNN.As
        self.mult_max = PCBNN.mult_max
        self.verbose = PCBNN.verbose

        self.ksp_Atildes = PCBNN.ksp_Atildes
        _,Atildes = self.ksp_Atildes.getOperators()
        self.Atildes = Atildes

        works, _ = self.Ms.getVecs()
        self.works = works


#compute the kernel of the self.ksp_Atildes operator and initialize local coarse space with it
        if self.ksp_Atildes.pc.getFactorSolverType() == 'mumps':
            self.ksp_Atildes.pc.setFactorSetUpSolverType()
            F = self.ksp_Atildes.pc.getFactorMatrix()
            F.setMumpsIcntl(7, 2)
            F.setMumpsIcntl(24, 1)
            F.setMumpsCntl(3, self.mumpsCntl3)
            self.ksp_Atildes.pc.setUp()
            nrb = F.getMumpsInfog(28)

            V0s = []
            for i in range(nrb):
                F.setMumpsIcntl(25, i+1)
                works.set(0.)
                self.ksp_Atildes.solve(works, works)
                V0s.append(works.copy())

            F.setMumpsIcntl(25, 0)
            if self.verbose:
                PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors as zero energy modes of local solver'.format(mpi.COMM_WORLD.rank, nrb), comm=self.comm)
        else:
            V0s = []
            nrb = 0


        if self.GenEO == True:
            #thresholds for the eigenvalue problems are computed from the user chosen targets for eigmin and eigmax
            tau_eigmin = self.eigmin
            if self.eigmax > 0:
                tau_eigmax = self.mult_max/self.eigmax
            else:
                tau_eigmax = 0
            if self.Atildes != self.As:
                self.solve_GenEO_eigmax(V0s, tau_eigmax)
            else:
                if self.verbose:
                    PETSc.Sys.Print('This is classical additive Schwarz so eigmax = {} (+1 if fully additive preconditioner), no eigenvalue problem will be solved for eigmax'.format(self.mult_max), comm=mpi.COMM_WORLD)
            if self.Atildes != self.Ms:
                self.solve_GenEO_eigmin(V0s, tau_eigmin)
            else:
                if self.verbose:
                    PETSc.Sys.Print('This is BNN so eigmin = 1, no eigenvalue problem will be solved for eigmin', comm=mpi.COMM_WORLD)

        V0, AV0, Delta, ksp_Delta = self.assemble_coarse_operators(V0s)

        self.V0 = V0
        self.AV0 = AV0
        self.Delta = Delta
        self.ksp_Delta = ksp_Delta

        self.gamma = PETSc.Vec().create(comm=PETSc.COMM_SELF)
        self.gamma.setType(PETSc.Vec.Type.SEQ)
        self.gamma.setSizes(len(self.V0))
        self.gamma_tmp = self.gamma.duplicate()

    def solve_GenEO_eigmax(self, V0s, tauGenEO_eigmax):
        """
        Solves the local GenEO eigenvalue problem related to the largest eigenvalue eigmax.

        Parameters
        ==========

        V0s : list of local PETSc .vecs
            V0s may already contain some local coarse vectors. This routine will possibly add more vectors to the list.

        tauGenEO_eigmax: Real.
            Threshold for selecting eigenvectors for the coarse space.

        """
        if tauGenEO_eigmax > 0:
            eps = SLEPc.EPS().create(comm=PETSc.COMM_SELF)
            eps.setDimensions(nev=self.nev)

            eps.setProblemType(SLEPc.EPS.ProblemType.GHIEP)
            eps.setOperators(self.Atildes , self.As )
            eps.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_REAL)
            eps.setTarget(0.)
            if len(V0s) > 0 :
                eps.setDeflationSpace(V0s)
            ST = eps.getST()
            ST.setType("sinvert")
            ST.setKSP(self.ksp_Atildes)
            eps.solve()
            if eps.getConverged() < self.nev:
                PETSc.Sys.Print('WARNING: Only {} eigenvalues converged for GenEO_eigmax in subdomain {} whereas {} were requested'.format(eps.getConverged(), mpi.COMM_WORLD.rank, self.nev), comm=self.comm)
            if abs(eps.getEigenvalue(eps.getConverged() -1)) < tauGenEO_eigmax:
                PETSc.Sys.Print('WARNING: The largest eigenvalue computed for GenEO_eigmax in subdomain {} is {} < the threshold which is {}. Consider setting PCBNN_GenEO_nev to something larger than {}'.format(mpi.COMM_WORLD.rank, eps.getEigenvalue(eps.getConverged() - 1), tauGenEO_eigmax, eps.getConverged()), comm=self.comm)

            for i in range(min(eps.getConverged(),self.maxev)):
                if(abs(eps.getEigenvalue(i))<tauGenEO_eigmax): #TODO tell slepc that the eigenvalues are real
                    V0s.append(self.works.duplicate())
                    eps.getEigenvector(i,V0s[-1])
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamax in subdomain {}: {}'.format(i, mpi.COMM_WORLD.rank, eps.getEigenvalue(i)) , comm=self.comm)
                else:
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamax in subdomain {}: {} <-- not selected (> {})'.format(i, mpi.COMM_WORLD.rank, eps.getEigenvalue(i), tauGenEO_eigmax), comm=self.comm)

            self.eps_eigmax=eps #TODO FIX the only reason for this line is to make sure self.ksp_Atildes and hence PCBNN.ksp is not destroyed
        if self.verbose:
            PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors after first GenEO'.format(mpi.COMM_WORLD.rank, len(V0s)), comm=self.comm)

    def solve_GenEO_eigmin(self, V0s, tauGenEO_eigmin):
        """
        Solves the local GenEO eigenvalue problem related to the smallest eigenvalue eigmin.

        Parameters
        ==========

        V0s : list of local PETSc .vecs
            V0s may already contain some local coarse vectors. This routine will possibly add more vectors to the list.

        tauGenEO_eigmin: Real.
            Threshold for selecting eigenvectors for the coarse space.

        """
        if tauGenEO_eigmin > 0:
            #to compute the smallest eigenvalues of the preconditioned matrix, Ms must be factorized
            tempksp = PETSc.KSP().create(comm=PETSc.COMM_SELF)
            tempksp.setOperators(self.Ms)
            tempksp.setType('preonly')
            temppc = tempksp.getPC()
            temppc.setType('cholesky')
            temppc.setFactorSolverType('mumps')
            temppc.setFactorSetUpSolverType()
            tempF = temppc.getFactorMatrix()
            tempF.setMumpsIcntl(7, 2)
            tempF.setMumpsIcntl(24, 1)
            tempF.setMumpsCntl(3, self.mumpsCntl3)
            temppc.setUp()
            tempnrb = tempF.getMumpsInfog(28)

            for i in range(tempnrb):
                tempF.setMumpsIcntl(25, i+1)
                self.works.set(0.)
                tempksp.solve(self.works, self.works)
                V0s.append(self.works.copy())

            tempF.setMumpsIcntl(25, 0)
            if self.verbose:
                PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors as zero energy modes of the scaled local operator (in GenEO for eigmin)'.format(mpi.COMM_WORLD.rank, tempnrb), comm=self.comm)

            #Eigenvalue Problem for smallest eigenvalues
            eps = SLEPc.EPS().create(comm=PETSc.COMM_SELF)
            eps.setDimensions(nev=self.nev)

            eps.setProblemType(SLEPc.EPS.ProblemType.GHIEP)
            eps.setOperators(self.Ms,self.Atildes)
            eps.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_REAL)
            eps.setTarget(0.)
            ST = eps.getST()

            ST.setType("sinvert")
            ST.setKSP(tempksp)

            if len(V0s) > 0 :
                eps.setDeflationSpace(V0s)
            eps.solve()
            if eps.getConverged() < self.nev:
                PETSc.Sys.Print('WARNING: Only {} eigenvalues converged for GenEO_eigmin in subdomain {} whereas {} were requested'.format(eps.getConverged(), mpi.COMM_WORLD.rank, self.nev), comm=self.comm)
            for i in range(min(eps.getConverged(),self.maxev)):
                if(abs(eps.getEigenvalue(i))<tauGenEO_eigmin): #TODO tell slepc that the eigenvalues are real
                   V0s.append(self.works.duplicate())
                   eps.getEigenvector(i,V0s[-1])
                   if self.verbose:
                       PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamin in subdomain {}: {}'.format(i, mpi.COMM_WORLD.rank, eps.getEigenvalue(i)), comm=self.comm)
                else:
                    if self.verbose:
                        PETSc.Sys.Print('GenEO eigenvalue number {} for lambdamin in subdomain {}: {} <-- not selected (> {})'.format(i, mpi.COMM_WORLD.rank, eps.getEigenvalue(i), tauGenEO_eigmin), comm=self.comm)
            self.eps_eigmin=eps #the only reason for this line is to make sure self.ksp_Atildes and hence PCBNN.ksp is not destroyed

    def assemble_coarse_operators(self,V0s):
        """
        Assembles the coarse operators from a list of local contributions to the coarse space.

        Parameters
        ==========

        V0s : list of local PETSc .vecs
           list of the coarse vectors contributed by the subdomain.

        Returns
        ==========

        V0 : list of local vectors or None ? FIX
            list of the local contributions to the coarse space numbered globally: V0[i] is either a scaled local vector from V0s or None if coarse vector number i belongs to another subdomain. The scaling that is applied to the coarse vectors ensures that their A-norm is 1.

        AV0 : list of global PETSc.Vecs
            list of the A*V0[i]. These are global vectors so not in the same format as the vectors in V0.

        Delta : PETSc.Mat (local)
            matrix of the coarse problem. As a result of the scaling of the coarse vectors, its diagonal is 1. This matrix is duplicated over all subdomains This matrix is duplicated over all subdomains

        ksp_Delta : PETSc.ksp
            Krylov subspace solver for the coarse problem matrix Delta.

        """
        if self.verbose:
            PETSc.Sys.Print('Subdomain number {} contributes {} coarse vectors in total'.format(mpi.COMM_WORLD.rank, len(V0s)), comm=self.comm)

        V0 = []
        for i in range(mpi.COMM_WORLD.size):
            nrbl = len(V0s) if i == mpi.COMM_WORLD.rank else None
            nrbl = mpi.COMM_WORLD.bcast(nrbl, root=i)
            for irbm in range(nrbl):
                V0.append(V0s[irbm] if i == mpi.COMM_WORLD.rank else None)

        AV0 = []
        work, _ = self.work
        for vec in V0:
            if vec:
                self.works = vec.copy()
            else:
                self.works.set(0.)
            work.set(0)
            self.scatter_l2g(self.works, work, PETSc.InsertMode.ADD_VALUES)
            AV0.append(self.A*work)
            self.scatter_l2g(AV0[-1], self.works, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.SCATTER_REVERSE)
            if vec:
                vec.scale(1./np.sqrt(vec.dot(self.works)))
                self.works = vec.copy()
            else:
                self.works.set(0)
            work.set(0)
            self.scatter_l2g(self.works, work, PETSc.InsertMode.ADD_VALUES)
            AV0[-1] = self.A*work

        PETSc.Sys.Print('There are {} vectors in the coarse space.'.format(len(V0)), comm=mpi.COMM_WORLD)

        #Define, fill and factorize coarse problem matrix
        Delta = PETSc.Mat().create(comm=PETSc.COMM_SELF)
        Delta.setType(PETSc.Mat.Type.SEQDENSE)
        Delta.setSizes([len(V0),len(V0)])
        Delta.setOption(PETSc.Mat.Option.SYMMETRIC, True)
        Delta.setPreallocationDense(None)
        for i, vec in enumerate(V0):
            if vec:
                self.works = vec.copy()
            else:
                self.works.set(0)

            work.set(0)
            self.scatter_l2g(self.works, work, PETSc.InsertMode.ADD_VALUES)
            for j in range(i+1):
                tmp = AV0[j].dot(work)
                Delta[i, j] = tmp
                Delta[j, i] = tmp
        Delta.assemble()
        ksp_Delta = PETSc.KSP().create(comm=PETSc.COMM_SELF)
        ksp_Delta.setOperators(Delta)
        ksp_Delta.setType('preonly')
        pc = ksp_Delta.getPC()
        pc.setType('cholesky')
        return V0, AV0, Delta, ksp_Delta


    def project(self, x):
        """
        Applies the coarse projection (or projection preconditioner) to x

        Parameters
        ==========

        x : PETSc.Vec
           Vector to which the projection is applied and in which the result is stored.

        """
        alpha = self.gamma.duplicate()
        for i, Avec in enumerate(self.AV0):
            self.gamma[i] = Avec.dot(x)

        self.ksp_Delta.solve(self.gamma, alpha)

        self.works.set(0)
        for i, vec in enumerate(self.V0):
            if vec:
                self.works.axpy(-alpha[i], vec)

        self.scatter_l2g(self.works, x, PETSc.InsertMode.ADD_VALUES)

    def coarse_init(self, rhs):
        """
        Initialize the projected PCG algorithm or MPCG algorithm with the solution of the problem in the coarse space.

        Parameters
        ==========

        rhs : PETSc.Vec
           Right hand side vector for which to initialize the problem.

        Returns
        =======

        out : PETSc.Vec
           Solution of the problem projected into the coarse space for the initialization of a projected Krylov subspace solver.

        """

        alpha = self.gamma.duplicate()

        self.scatter_l2g(rhs, self.works, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.SCATTER_REVERSE)
        self.gamma.set(0)
        self.gamma_tmp.set(0)
        for i, vec in enumerate(self.V0):
            if vec:
                self.gamma_tmp[i] = vec.dot(self.works)

        mpi.COMM_WORLD.Allreduce([self.gamma_tmp, mpi.DOUBLE], [self.gamma, mpi.DOUBLE], mpi.SUM)

        self.ksp_Delta.solve(self.gamma, alpha)

        self.works.set(0)
        for i, vec in enumerate(self.V0):
            if vec:
                self.works.axpy(alpha[i], vec)

        out = rhs.duplicate()
        out.set(0)
        self.scatter_l2g(self.works, out, PETSc.InsertMode.ADD_VALUES)
        return out

    def project_transpose(self, x):
        """
        Applies the transpose of the coarse projection (which is also a projection) to x

        Parameters
        ==========

        x : PETSc.Vec
           Vector to which the projection is applied and in which the result is stored.

        """
        alpha = self.gamma.duplicate()

        self.scatter_l2g(x, self.works, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.SCATTER_REVERSE)
        self.gamma.set(0)
        self.gamma_tmp.set(0)
        for i, vec in enumerate(self.V0):
            if vec:
                self.gamma_tmp[i] = vec.dot(self.works)

        mpi.COMM_WORLD.Allreduce([self.gamma_tmp, mpi.DOUBLE], [self.gamma, mpi.DOUBLE], mpi.SUM)
        self.ksp_Delta.solve(self.gamma, alpha)

        for i in range(len(self.V0)):
            x.axpy(-alpha[i], self.AV0[i])

back to top

Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API