agd.LinearPDE
This module is DEPRECATED, but kept for compatibility.
1# Copyright 2020 Jean-Marie Mirebeau, University Paris-Sud, CNRS, University Paris-Saclay 2# Distributed WITHOUT ANY WARRANTY. Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 3 4""" 5This module is DEPRECATED, but kept for compatibility. 6""" 7 8import numpy as np 9import scipy.sparse as sp 10 11from . import Selling 12from . import LinearParallel as LP 13from .AutomaticDifferentiation.cupy_generic import get_array_module 14 15def OperatorMatrix(diff,omega=None,mult=None, \ 16 gridScale=1, 17 boundaryConditions='Periodic', 18 divergenceForm=False, 19 intrinsicDrift=False): 20 r""" 21 Constructs a linear operator sparse matrix, given as input 22 - an array `diff` of symmetric positive definite matrices, 23 with shape $(d,d,n_1,...,n_d)$ where $d$ is the domain dimension. 24 - an array `omega` of vectors (optionnal), with shape $(d,n_1,...,n_d)$. 25 - an array of scalars (optionnal), with shape $(n_1,...,n_d)$. 26 27 additional parameters 28 - a grid scale 29 - boundary conditions, possibly axis by axis 30 ('Periodic', 'Reflected', 'Neumann', 'Dirichlet') 31 - divergence form or not 32 33 The discretized operator is 34 $$ 35 {-} \mathrm{div}(D \nabla u) + < \\omega, \nabla u> + mult*u, 36 $$ 37 denoting $D:=$`diff` and $\\omega:=$`omega`. 38 39 Replace the first term with $\mathrm{Tr}(D \nabla^2 u)$ in the 40 non-divergence form case. 41 42 Returns : a list of triplets, for building a coo matrix 43 """ 44 # ----- Get the domain shape ----- 45 bounds = diff.shape[2:] 46 dim = len(bounds) 47 xp = get_array_module(diff) 48 if isinstance(boundaryConditions,str): 49 boundaryConditions = np.full( (dim,2), boundaryConditions) 50 elif len(boundaryConditions)!=dim: 51 raise ValueError("""OperatorMatrix error : 52 inconsistent boundary conditions""") 53 54 if diff.shape[:2]!=(dim,dim): 55 raise ValueError("OperatorMatrix error : inconsistent matrix dimensions") 56 57 # -------- Decompose the tensors -------- 58 coef,offset = Selling.Decomposition(diff) 59 nCoef = coef.shape[0] 60 61 # ------ Check bounds or apply periodic boundary conditions ------- 62 grid = np.mgrid[tuple(slice(0,n) for n in bounds)] 63 grid = grid[:,None,...] 64 grid = xp.asarray(grid,dtype=offset.dtype) 65 66 neighPos = grid + offset 67 neighNeg = grid - offset 68 neumannPos = np.full_like(coef,False,dtype=bool) 69 neumannNeg = np.full_like(coef,False,dtype=bool) 70 dirichletPos = np.full_like(coef,False,dtype=bool) 71 dirichletNeg = np.full_like(coef,False,dtype=bool) 72 73 for neigh_,neumann,dirichlet in zip( (neighPos,neighNeg), (neumannPos,neumannNeg), (dirichletPos,dirichletNeg) ): 74 for neigh,cond_,bound in zip(neigh_,boundaryConditions,bounds): # Component by component 75 for out,cond in zip( (neigh<0,neigh>=bound), cond_): 76 if cond=='Periodic': 77 neigh[out] %= bound 78 elif cond=='Neumann': 79 neumann[out] = True 80 elif cond=='Dirichlet': 81 dirichlet[out] = True 82 83 # ------- Get the neighbor indices -------- 84 # Cumulative product in reverse order, omitting last term, beginning with 1 85 cum = tuple(list(reversed(np.cumprod(list(reversed(bounds+(1,)))))))[1:] 86 bCum = np.broadcast_to( np.reshape(cum, (dim,)+(1,)*(dim+1)), offset.shape) 87 bCum = xp.asarray(bCum,dtype=grid.dtype) 88 89 index = (grid*bCum).sum(0) 90 indexPos = (neighPos*bCum).sum(0) 91 indexNeg = (neighNeg*bCum).sum(0) 92 index = np.broadcast_to(index,indexPos.shape) 93 94 # ------- Get the coefficients for the first order term ----- 95 if omega is not None: 96 if intrinsicDrift: 97 eta=omega 98 else: 99 eta = LP.dot_AV(LP.inverse(diff),omega) 100 101 scalEta = LP.dot_VV(offset.astype(float), 102 np.broadcast_to(np.reshape(eta,(dim,1,)+bounds),offset.shape)) 103 coefOmega = coef*scalEta 104 105 # ------- Create the triplets ------ 106 107 # Second order part 108 # Nemann : remove all differences which are not inside (a.k.a multiply coef by inside) 109 # TODO : Dirichlet : set to zero the coef only for the outside part 110 111 coef = coef.reshape(-1)/ (gridScale**2) # Take grid scale into account 112 113 index = index.reshape(-1) 114 indexPos = indexPos.reshape(-1) 115 indexNeg = indexNeg.reshape(-1) 116 117 nff = lambda t : np.logical_not(t).astype(float).reshape(-1) 118 IP = nff(np.logical_or(neumannPos,dirichletPos)) 119 IN = nff(np.logical_or(neumannNeg,dirichletNeg)) 120 iP = nff(neumannPos) 121 iN = nff(neumannNeg) 122 123 if divergenceForm: 124 row = np.concatenate((index, indexPos, index, indexPos)) 125 col = np.concatenate((index, index, indexPos, indexPos)) 126 data = np.concatenate((iP*coef/2, -IP*coef/2, -IP*coef/2, IP*coef/2)) 127 128 row = np.concatenate(( row, index, indexNeg, index, indexNeg)) 129 col = np.concatenate(( col, index, index, indexNeg, indexNeg)) 130 data = np.concatenate((data, iN*coef/2, -IN*coef/2, -IN*coef/2, IN*coef/2)) 131 132 else: 133 row = np.concatenate( (index, index, index)) 134 col = np.concatenate( (index, indexPos, indexNeg)) 135 data = np.concatenate((iP*coef+iN*coef, -IP*coef, -IN*coef)) 136 137 138 # First order part, using centered finite differences 139 if omega is not None: 140 coefOmega = coefOmega.flatten() / gridScale # Take grid scale in 141 row = np.concatenate((row, index, index)) 142 col = np.concatenate((col, indexPos, indexNeg)) 143 data= np.concatenate((data,IP*iN*coefOmega/2,-IN*iP*coefOmega/2)) 144 145 if mult is not None: 146 # TODO Non periodic boundary conditions 147 size=np.prod(bounds) 148 row = np.concatenate((row, range(size))) 149 col = np.concatenate((col, range(size))) 150 data= np.concatenate((data,mult.flatten())) 151 152 nz = data!=0 153 return data[nz],(row[nz],col[nz]) 154 155 156 157 158 159
def
OperatorMatrix( diff, omega=None, mult=None, gridScale=1, boundaryConditions='Periodic', divergenceForm=False, intrinsicDrift=False):
16def OperatorMatrix(diff,omega=None,mult=None, \ 17 gridScale=1, 18 boundaryConditions='Periodic', 19 divergenceForm=False, 20 intrinsicDrift=False): 21 r""" 22 Constructs a linear operator sparse matrix, given as input 23 - an array `diff` of symmetric positive definite matrices, 24 with shape $(d,d,n_1,...,n_d)$ where $d$ is the domain dimension. 25 - an array `omega` of vectors (optionnal), with shape $(d,n_1,...,n_d)$. 26 - an array of scalars (optionnal), with shape $(n_1,...,n_d)$. 27 28 additional parameters 29 - a grid scale 30 - boundary conditions, possibly axis by axis 31 ('Periodic', 'Reflected', 'Neumann', 'Dirichlet') 32 - divergence form or not 33 34 The discretized operator is 35 $$ 36 {-} \mathrm{div}(D \nabla u) + < \\omega, \nabla u> + mult*u, 37 $$ 38 denoting $D:=$`diff` and $\\omega:=$`omega`. 39 40 Replace the first term with $\mathrm{Tr}(D \nabla^2 u)$ in the 41 non-divergence form case. 42 43 Returns : a list of triplets, for building a coo matrix 44 """ 45 # ----- Get the domain shape ----- 46 bounds = diff.shape[2:] 47 dim = len(bounds) 48 xp = get_array_module(diff) 49 if isinstance(boundaryConditions,str): 50 boundaryConditions = np.full( (dim,2), boundaryConditions) 51 elif len(boundaryConditions)!=dim: 52 raise ValueError("""OperatorMatrix error : 53 inconsistent boundary conditions""") 54 55 if diff.shape[:2]!=(dim,dim): 56 raise ValueError("OperatorMatrix error : inconsistent matrix dimensions") 57 58 # -------- Decompose the tensors -------- 59 coef,offset = Selling.Decomposition(diff) 60 nCoef = coef.shape[0] 61 62 # ------ Check bounds or apply periodic boundary conditions ------- 63 grid = np.mgrid[tuple(slice(0,n) for n in bounds)] 64 grid = grid[:,None,...] 65 grid = xp.asarray(grid,dtype=offset.dtype) 66 67 neighPos = grid + offset 68 neighNeg = grid - offset 69 neumannPos = np.full_like(coef,False,dtype=bool) 70 neumannNeg = np.full_like(coef,False,dtype=bool) 71 dirichletPos = np.full_like(coef,False,dtype=bool) 72 dirichletNeg = np.full_like(coef,False,dtype=bool) 73 74 for neigh_,neumann,dirichlet in zip( (neighPos,neighNeg), (neumannPos,neumannNeg), (dirichletPos,dirichletNeg) ): 75 for neigh,cond_,bound in zip(neigh_,boundaryConditions,bounds): # Component by component 76 for out,cond in zip( (neigh<0,neigh>=bound), cond_): 77 if cond=='Periodic': 78 neigh[out] %= bound 79 elif cond=='Neumann': 80 neumann[out] = True 81 elif cond=='Dirichlet': 82 dirichlet[out] = True 83 84 # ------- Get the neighbor indices -------- 85 # Cumulative product in reverse order, omitting last term, beginning with 1 86 cum = tuple(list(reversed(np.cumprod(list(reversed(bounds+(1,)))))))[1:] 87 bCum = np.broadcast_to( np.reshape(cum, (dim,)+(1,)*(dim+1)), offset.shape) 88 bCum = xp.asarray(bCum,dtype=grid.dtype) 89 90 index = (grid*bCum).sum(0) 91 indexPos = (neighPos*bCum).sum(0) 92 indexNeg = (neighNeg*bCum).sum(0) 93 index = np.broadcast_to(index,indexPos.shape) 94 95 # ------- Get the coefficients for the first order term ----- 96 if omega is not None: 97 if intrinsicDrift: 98 eta=omega 99 else: 100 eta = LP.dot_AV(LP.inverse(diff),omega) 101 102 scalEta = LP.dot_VV(offset.astype(float), 103 np.broadcast_to(np.reshape(eta,(dim,1,)+bounds),offset.shape)) 104 coefOmega = coef*scalEta 105 106 # ------- Create the triplets ------ 107 108 # Second order part 109 # Nemann : remove all differences which are not inside (a.k.a multiply coef by inside) 110 # TODO : Dirichlet : set to zero the coef only for the outside part 111 112 coef = coef.reshape(-1)/ (gridScale**2) # Take grid scale into account 113 114 index = index.reshape(-1) 115 indexPos = indexPos.reshape(-1) 116 indexNeg = indexNeg.reshape(-1) 117 118 nff = lambda t : np.logical_not(t).astype(float).reshape(-1) 119 IP = nff(np.logical_or(neumannPos,dirichletPos)) 120 IN = nff(np.logical_or(neumannNeg,dirichletNeg)) 121 iP = nff(neumannPos) 122 iN = nff(neumannNeg) 123 124 if divergenceForm: 125 row = np.concatenate((index, indexPos, index, indexPos)) 126 col = np.concatenate((index, index, indexPos, indexPos)) 127 data = np.concatenate((iP*coef/2, -IP*coef/2, -IP*coef/2, IP*coef/2)) 128 129 row = np.concatenate(( row, index, indexNeg, index, indexNeg)) 130 col = np.concatenate(( col, index, index, indexNeg, indexNeg)) 131 data = np.concatenate((data, iN*coef/2, -IN*coef/2, -IN*coef/2, IN*coef/2)) 132 133 else: 134 row = np.concatenate( (index, index, index)) 135 col = np.concatenate( (index, indexPos, indexNeg)) 136 data = np.concatenate((iP*coef+iN*coef, -IP*coef, -IN*coef)) 137 138 139 # First order part, using centered finite differences 140 if omega is not None: 141 coefOmega = coefOmega.flatten() / gridScale # Take grid scale in 142 row = np.concatenate((row, index, index)) 143 col = np.concatenate((col, indexPos, indexNeg)) 144 data= np.concatenate((data,IP*iN*coefOmega/2,-IN*iP*coefOmega/2)) 145 146 if mult is not None: 147 # TODO Non periodic boundary conditions 148 size=np.prod(bounds) 149 row = np.concatenate((row, range(size))) 150 col = np.concatenate((col, range(size))) 151 data= np.concatenate((data,mult.flatten())) 152 153 nz = data!=0 154 return data[nz],(row[nz],col[nz])
Constructs a linear operator sparse matrix, given as input
- an array
diff
of symmetric positive definite matrices, with shape $(d,d,n_1,...,n_d)$ where $d$ is the domain dimension. - an array
omega
of vectors (optionnal), with shape $(d,n_1,...,n_d)$. - an array of scalars (optionnal), with shape $(n_1,...,n_d)$.
additional parameters
- a grid scale
- boundary conditions, possibly axis by axis ('Periodic', 'Reflected', 'Neumann', 'Dirichlet')
- divergence form or not
The discretized operator is
$$
{-} \mathrm{div}(D \nabla u) + < \omega, \nabla u> + mult*u,
$$
denoting $D:=$diff
and $\omega:=$omega
.
Replace the first term with $\mathrm{Tr}(D \nabla^2 u)$ in the non-divergence form case.
Returns : a list of triplets, for building a coo matrix