Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
31aa52b
Add copy(), set_scalar and fix BLO __add__
jowezarek Mar 13, 2025
2c0b9a7
add tests and copy to KroneckerLinearSolver
jowezarek Mar 13, 2025
9511c9d
add more copy() functions
jowezarek Mar 13, 2025
4c2195b
add one more copy()
jowezarek Mar 13, 2025
52b7115
undo some ZeroOperator changes, two more instances of copy()
jowezarek Mar 14, 2025
ec3d2c0
fix imports in test_linalg
jowezarek Mar 14, 2025
486aa3d
add copy() to 6 DistributedFFTBase subclasses
jowezarek Mar 20, 2025
5191fcc
use np.random.default_rng and adjust copy method
jowezarek Mar 20, 2025
a6d758b
remove three copy() instances in BLO
jowezarek Mar 20, 2025
644455d
re-add copy() to __iadd__ method
jowezarek Mar 21, 2025
fb60def
remove ZeroLO import
jowezarek Mar 25, 2025
580f70d
fix faulty test
jowezarek Mar 25, 2025
55264fc
re-undo copy() instances in block __add__ and __sub__
jowezarek Mar 25, 2025
d55f399
Merge branch 'devel' into fix_BLO_addition
yguclu Apr 16, 2025
44d6475
Create test_linear_operator_rules.py
yguclu Apr 16, 2025
586b6b7
Update test_linalg.py
yguclu Apr 16, 2025
ead08e1
Only run linear algebra tests
yguclu Apr 23, 2025
6868e45
Move test_linear_operator_rules.py to the correct folder
yguclu Apr 23, 2025
43121cd
Fix test_linear_operator_rules.py
yguclu Apr 23, 2025
c78be77
Add BlockLinearOperator test in test_linear_operator_rules.py
yguclu Apr 23, 2025
aa48465
Remove __imul__, __iadd__, __isub__ from BlockLinearOperator
yguclu Apr 23, 2025
4bc54bb
Add missing imports into test_linear_operator_rules.py
yguclu Apr 23, 2025
4a2ab8a
Add missing import in test_linear_operator_rules.py
yguclu Apr 23, 2025
68ca906
Run fewer tests on macOS
yguclu Apr 23, 2025
c1fd8c8
Add rules on composition with identity
yguclu May 7, 2025
04208b7
Merge branch 'devel' into fix_BLO_addition
yguclu May 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions psydac/feec/multipatch/fem_linear_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def T(self):
@property
def dtype( self ):
return self.domain.dtype

def copy(self):
return FemLinearOperator(self.fem_domain, self.fem_codomain, self.matrix, self._sparse_matrix)

def toarray(self):
return self._matrix.toarray()
Expand Down Expand Up @@ -135,6 +138,9 @@ def __init__( self, operators ):

# matrix not defined by matrix product because it could break the Stencil Matrix structure

def copy(self):
return ComposedLinearOperator(self._operators)

def to_sparse_matrix( self, **kwargs):
mat = self._operators[-1].to_sparse_matrix()
for i in range(2, self._n+1):
Expand All @@ -160,6 +166,9 @@ class IdLinearOperator( FemLinearOperator ):
def __init__( self, V ):
FemLinearOperator.__init__(self, fem_domain=V)

def copy(self):
return IdLinearOperator(self.fem_domain)

def to_sparse_matrix( self , **kwargs):
return sparse_id( self.fem_domain.nbasis )

Expand All @@ -183,6 +192,9 @@ def __init__( self, B, A ):
self._A = A
self._B = B

def copy(self):
return SumLinearOperator(self._B, self._A)

def to_sparse_matrix( self, **kwargs):
return self._A.to_sparse_matrix() + self._B.to_sparse_matrix()

Expand All @@ -205,6 +217,9 @@ def __init__( self, c, A ):
self._A = A
self._c = c

def copy(self):
return MultLinearOperator(self._c, self._A)

def to_sparse_matrix( self, **kwargs):
return self._c * self._A.to_sparse_matrix()

Expand Down
21 changes: 21 additions & 0 deletions psydac/feec/multipatch/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ def __init__(
storage_fn)
save_npz(storage_fn, self._sparse_matrix)

def copy(self):
raise NotImplementedError

def set_homogenous_bc(self, boundary, rhs=None):
domain = self.symbolic_domain
Vh = self.fem_domain
Expand Down Expand Up @@ -721,6 +724,9 @@ def __init__(
storage_fn)
save_npz(storage_fn, self._sparse_matrix)

def copy(self):
raise NotImplementedError

def set_homogenous_bc(self, boundary):
domain = self.symbolic_domain
Vh = self.fem_domain
Expand Down Expand Up @@ -1011,6 +1017,9 @@ def __init__(
# matrices are not stored, we will probably compute them later
pass

def copy(self):
raise NotImplementedError

def to_sparse_matrix(self):
"""
the Hodge matrix is the patch-wise multi-patch mass matrix
Expand Down Expand Up @@ -1098,6 +1107,9 @@ def __init__(self, V0h, V1h):
def transpose(self, conjugate=False):
# todo (MCP): define as the dual differential operator
return BrokenTransposedGradient_2D(self.fem_domain, self.fem_codomain)

def copy(self):
return BrokenGradient_2D(self.fem_domain, self.fem_codomain)

# ==============================================================================

Expand All @@ -1116,6 +1128,9 @@ def __init__(self, V0h, V1h):
def transpose(self, conjugate=False):
# todo (MCP): discard
return BrokenGradient_2D(self.fem_codomain, self.fem_domain)

def copy(self):
return BrokenTransposedGradient_2D(self.fem_domain, self.fem_codomain)


# ==============================================================================
Expand All @@ -1132,6 +1147,9 @@ def __init__(self, V1h, V2h):
def transpose(self, conjugate=False):
return BrokenTransposedScalarCurl_2D(
V1h=self.fem_domain, V2h=self.fem_codomain)

def copy(self):
return BrokenScalarCurl_2D(self.fem_domain, self.fem_codomain)


# ==============================================================================
Expand All @@ -1148,6 +1166,9 @@ def __init__(self, V1h, V2h):

def transpose(self, conjugate=False):
return BrokenScalarCurl_2D(V1h=self.fem_codomain, V2h=self.fem_domain)

def copy(self):
return BrokenTransposedScalarCurl_2D(self.fem_domain, self.fem_codomain)


# ==============================================================================
Expand Down
24 changes: 24 additions & 0 deletions psydac/linalg/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ def codomain(self):
def dtype(self):
pass

@abstractmethod
def copy(self):
pass

@abstractmethod
def tosparse(self):
pass
Expand Down Expand Up @@ -584,6 +588,14 @@ def operator(self):
@property
def dtype(self):
return None

def copy(self):
return ScaledLinearOperator(self.domain, self.codomain, self.scalar, self.operator)

def set_scalar(self, c):
assert np.isscalar(c)
assert np.iscomplexobj(c) == (self.codomain._dtype == complex)
self._scalar = c

def toarray(self):
return self._scalar*self._operator.toarray()
Expand Down Expand Up @@ -667,6 +679,9 @@ def addends(self):
@property
def dtype(self):
return None

def copy(self):
return SumLinearOperator(self.domain, self.codomain, *self.addends)

def toarray(self):
out = np.zeros(self.shape, dtype=self.dtype)
Expand Down Expand Up @@ -798,6 +813,9 @@ def multiplicants(self):
@property
def dtype(self):
return None

def copy(self):
return ComposedLinearOperator(self.domain, self.codomain, *self.multiplicants)

def toarray(self):
raise NotImplementedError('toarray() is not defined for ComposedLinearOperators.')
Expand Down Expand Up @@ -905,6 +923,9 @@ def operator(self):
def factorial(self):
""" Returns the power to which the operator is raised. """
return self._factorial

def copy(self):
return PowerLinearOperator(self.domain, self.codomain, self.operator, self.factorial)

def toarray(self):
raise NotImplementedError('toarray() is not defined for PowerLinearOperators.')
Expand Down Expand Up @@ -1169,6 +1190,9 @@ def codomain(self):
@property
def dtype(self):
return None

def copy(self):
return MatrixFreeLinearOperator(self.domain, self.codomain, self._dot, self._dot_transpose)

def dot(self, v, out=None, **kwargs):
assert isinstance(v, Vector)
Expand Down
17 changes: 10 additions & 7 deletions psydac/linalg/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ def __mul__(self, a):

# ...
def __add__(self, M):
from psydac.linalg.basic import ZeroOperator
if not isinstance(M, BlockLinearOperator):
return LinearOperator.__add__(self, M)

Expand All @@ -737,9 +738,9 @@ def __add__(self, M):
for ij in set(self._blocks.keys()) | set(M._blocks.keys()):
Bij = self[ij]
Mij = M[ij]
if Bij is None: blocks[ij] = Mij.copy()
elif Mij is None: blocks[ij] = Bij.copy()
else : blocks[ij] = Bij + Mij
if Bij is None or isinstance(Bij, ZeroOperator): blocks[ij] = Mij.copy() if Mij is not None else None
elif Mij is None or isinstance(Mij, ZeroOperator): blocks[ij] = Bij.copy()
else : blocks[ij] = Bij + Mij
mat = BlockLinearOperator(self.domain, self.codomain, blocks=blocks)
if len(mat._blocks) != len(self._blocks):
mat.set_backend(self._backend)
Expand All @@ -752,6 +753,7 @@ def __add__(self, M):

# ...
def __sub__(self, M):
from psydac.linalg.basic import ZeroOperator
if not isinstance(M, BlockLinearOperator):
return LinearOperator.__sub__(self, M)

Expand All @@ -761,9 +763,9 @@ def __sub__(self, M):
for ij in set(self._blocks.keys()) | set(M._blocks.keys()):
Bij = self[ij]
Mij = M[ij]
if Bij is None: blocks[ij] = -Mij
elif Mij is None: blocks[ij] = Bij.copy()
else : blocks[ij] = Bij - Mij
if Bij is None : blocks[ij] = -Mij
elif Mij is None or isinstance(Mij, ZeroOperator): blocks[ij] = Bij.copy()
else : blocks[ij] = Bij - Mij
mat = BlockLinearOperator(self.domain, self.codomain, blocks=blocks)
if len(mat._blocks) != len(self._blocks):
mat.set_backend(self._backend)
Expand Down Expand Up @@ -1254,7 +1256,8 @@ def set_backend(self, backend):

if not all(isinstance(b, (StencilMatrix, StencilInterfaceMatrix)) for b in self._blocks.values()):
for b in self._blocks.values():
b.set_backend(backend)
if getattr(b, "set_backend", None) is not None:
b.set_backend(backend)
return

block_shape = (self.n_block_rows, self.n_block_cols)
Expand Down
3 changes: 3 additions & 0 deletions psydac/linalg/fft.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def toarray(self):

def tosparse(self):
raise NotImplementedError('tosparse() is not defined for DistributedFFTBase.')

def copy(self):
raise NotImplementedError('copy() is not implemented for DistributedFFTBase.')

# Possible additions for the future:
# * split off the LinearSolver class when used with the space ndarray (as used in the KroneckerLinearSolver),
Expand Down
10 changes: 10 additions & 0 deletions psydac/linalg/kron.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def ndim( self ):
@property
def mats( self ):
return self._mats

def copy(self):
return KroneckerStencilMatrix(self.domain, self.codomain, *self.mats)

# ...
def dot(self, x, out=None):
Expand Down Expand Up @@ -257,6 +260,7 @@ def __init__(self, V, W, *args , with_pads=False):
self._codomain = W
self._mats = list(args)
self._ndim = len(args)
self._with_pads = with_pads

#--------------------------------------
# Abstract interface
Expand Down Expand Up @@ -284,6 +288,9 @@ def ndim(self):
@property
def mats(self):
return self._mats

def copy(self):
return KroneckerDenseMatrix(self.domain, self.codomain, *self.mats, self._with_pads)

# ...
def dot(self, x, out=None):
Expand Down Expand Up @@ -428,6 +435,9 @@ def __init__(self, V, W, solvers):

# for now: allocate temporary arrays here (can be removed later)
self._temp1, self._temp2 = self._allocate_temps()

def copy(self):
return KroneckerLinearSolver(self.domain, self.codomain, self.solvers)

def _setup_solvers(self):
"""
Expand Down
41 changes: 40 additions & 1 deletion psydac/linalg/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "p")}
self._info = None

def copy(self):
return ConjugateGradient(self.linop, x0=self.get_options("x0"), tol=self.get_options("tol"),
maxiter=self.get_options("maxiter"), verbose=self.get_options("verbose"),
recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Conjugate gradient algorithm for solving linear system Ax=b.
Expand Down Expand Up @@ -295,6 +300,11 @@ def __init__(self, A, *, pc=None, x0=None, tol=1e-6, maxiter=1000, verbose=False
self._tmps = {**tmps_codomain, **tmps_domain}
self._info = None

def copy(self):
return PConjugateGradient(self.linop, pc=self.get_options("pc"), x0=self.get_options("x0"),
tol=self.get_options("tol"), maxiter=self.get_options("maxiter"),
verbose=self.get_options("verbose"), recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Preconditioned Conjugate Gradient (PCG) solves the symetric positive definte
Expand Down Expand Up @@ -454,6 +464,11 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "p", "vs", "rs", "ps")}
self._info = None

def copy(self):
return BiConjugateGradient(self.linop, x0=self.get_options("x0"), tol=self.get_options("tol"),
maxiter=self.get_options("maxiter"), verbose=self.get_options("verbose"),
recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Biconjugate gradient (BCG) algorithm for solving linear system Ax=b.
Expand Down Expand Up @@ -643,6 +658,11 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "p", "vr", "r0")}
self._info = None

def copy(self):
return BiConjugateGradientStabilized(self.linop, x0=self.get_options("x0"), tol=self.get_options("tol"),
maxiter=self.get_options("maxiter"), verbose=self.get_options("verbose"),
recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Biconjugate gradient stabilized method (BCGSTAB) algorithm for solving linear system Ax=b.
Expand Down Expand Up @@ -843,6 +863,11 @@ def __init__(self, A, *, pc=None, x0=None, tol=1e-6, maxiter=1000, verbose=False
"rp0")}
self._info = None

def copy(self):
return PBiConjugateGradientStabilized(self.linop, pc=self.get_options("pc"), x0=self.get_options("x0"),
tol=self.get_options("tol"), maxiter=self.get_options("maxiter"),
verbose=self.get_options("verbose"), recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Preconditioned biconjugate gradient stabilized method (PBCGSTAB) algorithm for solving linear system Ax=b.
Expand Down Expand Up @@ -1085,6 +1110,11 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle
self._tmps = {key: self.domain.zeros() for key in ("res_old", "res_new", "w_new", "w_work", "w_old", "v", "y")}
self._info = None

def copy(self):
return MinimumResidual(self.linop, x0=self.get_options("x0"), tol=self.get_options("tol"),
maxiter=self.get_options("maxiter"), verbose=self.get_options("verbose"),
recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Use MINimum RESidual iteration to solve Ax=b
Expand Down Expand Up @@ -1399,6 +1429,11 @@ def __init__(self, A, *, x0=None, tol=None, atol=None, btol=None, maxiter=1000,
tmps_codomain = {key: self.codomain.zeros() for key in ("v", "v_work", "h", "hbar")}
self._tmps = {**tmps_codomain, **tmps_domain}

def copy(self):
return LSMR(self.linop, x0=self.get_options("x0"), tol=self.get_options("tol"), atol=self.get_options("atol"),
btol=self.get_options("btol"), maxiter=self.get_options("maxiter"), conlim=self.get_options("conlim"),
verbose=self.get_options("verbose"), recycle=self.get_options("recycle"))

def get_success(self):
return self._successful

Expand Down Expand Up @@ -1740,6 +1775,11 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=100, verbose=False, recycle=
self._Q = []
self._info = None

def copy(self):
return GMRES(self.linop, x0=self.get_options("x0"), tol=self.get_options("tol"),
maxiter=self.get_options("maxiter"), verbose=self.get_options("verbose"),
recycle=self.get_options("recycle"))

def solve(self, b, out=None):
"""
Generalized minimal residual algorithm for solving linear system Ax=b.
Expand Down Expand Up @@ -1911,4 +1951,3 @@ def apply_givens_rotation(self, k, sn, cn):

def dot(self, b, out=None):
return self.solve(b, out=out)

Loading