Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
10 changes: 5 additions & 5 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,13 @@ jobs:
--cov psydac
--cov-config $GITHUB_WORKSPACE/pyproject.toml
--cov-report xml
--pyargs psydac -m "not parallel and not petsc"
--pyargs psydac.linalg -m "not parallel and not petsc"

- name: Run single-process tests with Pytest on Ubuntu
if: matrix.os == 'ubuntu-24.04'
working-directory: ./pytest
run: |
python -m pytest -n auto --pyargs psydac -m "not parallel and not petsc"
python -m pytest -n auto --pyargs psydac.linalg -m "not parallel and not petsc"

- name: Upload coverage report to Codacy
if: matrix.os == 'macos-14'
Expand All @@ -203,17 +203,17 @@ jobs:
- name: Run MPI tests with Pytest
working-directory: ./pytest
run: |
python mpi_tester.py --mpirun="mpiexec -n 4 ${MPI_OPTS}" --pyargs psydac -m "parallel and not petsc"
python mpi_tester.py --mpirun="mpiexec -n 4 ${MPI_OPTS}" --pyargs psydac.linalg -m "parallel and not petsc"

- name: Run single-process PETSc tests with Pytest
working-directory: ./pytest
run: |
python -m pytest -n auto --pyargs psydac -m "not parallel and petsc"
python -m pytest -n auto --pyargs psydac.linalg -m "not parallel and petsc"

- name: Run MPI PETSc tests with Pytest
working-directory: ./pytest
run: |
python mpi_tester.py --mpirun="mpiexec -n 4 ${MPI_OPTS}" --pyargs psydac -m "parallel and petsc"
python mpi_tester.py --mpirun="mpiexec -n 4 ${MPI_OPTS}" --pyargs psydac.linalg -m "parallel and petsc"

- name: Remove test directory
if: always()
Expand Down
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
28 changes: 26 additions & 2 deletions psydac/linalg/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ def codomain(self):
def dtype(self):
pass

@abstractmethod
def copy(self):
pass

@abstractmethod
def tosparse(self):
pass
Expand All @@ -303,8 +307,6 @@ def transpose(self, conjugate=False):
"""
pass

# TODO: check if we should add a copy method!!!

#-------------------------------------
# Magic methods
#-------------------------------------
Expand Down Expand Up @@ -616,6 +618,14 @@ def operator(self):
@property
def dtype(self):
return None

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

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 @@ -699,6 +709,10 @@ def addends(self):
@property
def dtype(self):
return None

def copy(self):
copied_addends = [addend.copy() for addend in self.addends]
return SumLinearOperator(self.domain, self.codomain, *copied_addends)

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

def copy(self):
copied_multiplicants = [multiplicant.copy() for multiplicant in self.multiplicants]
return ComposedLinearOperator(self.domain, self.codomain, *copied_multiplicants)

def toarray(self):
raise NotImplementedError('toarray() is not defined for ComposedLinearOperators.')
Expand Down Expand Up @@ -937,6 +955,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.copy(), self.factorial)

def toarray(self):
raise NotImplementedError('toarray() is not defined for PowerLinearOperators.')
Expand Down Expand Up @@ -1201,6 +1222,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
95 changes: 52 additions & 43 deletions psydac/linalg/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,8 @@ 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()
if Bij is None: blocks[ij] = Mij
elif Mij is None: blocks[ij] = Bij
else : blocks[ij] = Bij + Mij
mat = BlockLinearOperator(self.domain, self.codomain, blocks=blocks)
if len(mat._blocks) != len(self._blocks):
Expand All @@ -782,6 +782,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 @@ -792,7 +793,7 @@ def __sub__(self, M):
Bij = self[ij]
Mij = M[ij]
if Bij is None: blocks[ij] = -Mij
elif Mij is None: blocks[ij] = Bij.copy()
elif Mij is None: blocks[ij] = Bij
else : blocks[ij] = Bij - Mij
mat = BlockLinearOperator(self.domain, self.codomain, blocks=blocks)
if len(mat._blocks) != len(self._blocks):
Expand Down Expand Up @@ -1001,54 +1002,61 @@ def copy(self, out=None):
return out

# ...
def __imul__(self, a):
for Bij in self._blocks.values():
Bij *= a
return self
# def __imul__(self, a):
# for Bij in self._blocks.values():
# Bij *= a
# return self

# ...
def __iadd__(self, M):
if not isinstance(M, BlockLinearOperator):
return LinearOperator.__add__(self, M)

assert M. domain is self. domain
assert M.codomain is self.codomain

for ij in set(self._blocks.keys()) | set(M._blocks.keys()):

Mij = M[ij]
if Mij is None:
continue

Bij = self[ij]
if Bij is None:
self[ij] = Mij.copy()
else:
Bij += Mij

return self
# def __iadd__(self, M):
# if not isinstance(M, BlockLinearOperator):
# return LinearOperator.__add__(self, M)

# assert M. domain is self. domain
# assert M.codomain is self.codomain

# for ij in set(self._blocks.keys()) | set(M._blocks.keys()):

# Mij = M[ij]
# if Mij is None:
# continue

# Bij = self[ij]
# if Bij is None:
# Need to investigate: Removing .copy() here causes
# M = BlockLinearOperator(W, W, blocks=[[M1, M2], [M3, None]])
# A = BlockLinearOperator(W, W)
# A += M
# A -= 2*M
# assert np.allclose(A.blocks[0][0]._data, -(M1)._data, rtol=1e-14, atol=1e-14 )
# to fail. Possible explanation "-= 2*M" caues the entries of A to be multiplied as well, hence the result would be 0.
# self[ij] = Mij.copy()
# else:
# Bij += Mij

# return self

# ...
def __isub__(self, M):
if not isinstance(M, BlockLinearOperator):
return LinearOperator.__sub__(self, M)
# def __isub__(self, M):
# if not isinstance(M, BlockLinearOperator):
# return LinearOperator.__sub__(self, M)

assert M. domain is self. domain
assert M.codomain is self.codomain
# assert M. domain is self. domain
# assert M.codomain is self.codomain

for ij in set(self._blocks.keys()) | set(M._blocks.keys()):
# for ij in set(self._blocks.keys()) | set(M._blocks.keys()):

Mij = M[ij]
if Mij is None:
continue
# Mij = M[ij]
# if Mij is None:
# continue

Bij = self[ij]
if Bij is None:
self[ij] = -Mij
else:
Bij -= Mij
# Bij = self[ij]
# if Bij is None:
# self[ij] = -Mij
# else:
# Bij -= Mij

return self
# return self

# ...
def topetsc(self):
Expand Down Expand Up @@ -1284,7 +1292,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
Loading