-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdifftools.py
More file actions
executable file
·238 lines (189 loc) · 7.65 KB
/
difftools.py
File metadata and controls
executable file
·238 lines (189 loc) · 7.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# difftools.py - Several useful utilities for numerical differentiation
#
# Author: Stefan Fuertinger [stefan.fuertinger@gmx.at]
# Created: June 13 2012
# Last modified: <2017-09-21 12:08:48>
from __future__ import division
import numpy as np
from scipy.sparse import spdiags, eye, kron
##########################################################################################
def fidop2d(N, drt='xy', fds='c'):
"""
Compute 2D finite difference operators
Parameters
----------
N : int
Positive integer holding the grid dimension (has to be square, i.e.
`N`-by-`N`!)
drt : str
String determining the direction of the discrete derivatives.
Can be either 'x', 'y' or 'xy'.
fds : str
String determining the employed finite difference scheme. Can be
either 'c' for centered, 'f' for forward or 'b' for backward.
Returns
-------
D : SciPy sparse matrix/matrices
Depending on the given input one or two sparse matrices
corresponding to the wanted discrete derivative operators are
returned.
Examples
--------
The command
>>> Dx,Dy = fidop2d(N)
returns the sparse `N**2`-by-`N**2` matrices `Dx` and `Dy` such that `Dh` defined by
>>> import numpy as np
>>> Dh = np.hstack([Dx,Dy])
is the discrete gradient operator in lexicographic
ordering for a function `F` given on a grid of size `N`-by-`N`.
The matrix `Dx` is a discrete approximation of the first-order derivative operator
in `x`-direction, Analogously, `Dy` discretizes the first order derivative operator
in `y`-direction. The spacing between points in each direction is assumed to be one
(i.e. the step size `h = 1`).
The command
>>> D = fidop2d(N,drt)
returns the sparse `N**2`-by-`N**2` matrix `D` corresponding to the discrete derivative
operator in direction `drt` in lexicographic ordering. The string `drt` has to be either 'x' (default)
, 'y' or 'xy'. Note: only 'xy' will return two matrices, namely `Dx`, `Dy`.
The command
>>> D = fidop2d(N,drt,fds)
returns sparse the `N**2`-by-`N**2` matrix `D` corresponding to the discrete derivative
operator in direction `drt` in lexicographic ordering using the finite difference scheme `fds`.
The string `fds` can either be 'c' (centered differences, in cells, default),
'f' (forward differences, in interfaces) or 'b' (backward differences, in interfaces).
To discretize first order differential operators on a 4-by-4 grid in both `x`- and `y`-directions
using forward differences use
>>> Dx,Dy = fidop2d(4,fds='f')
To get only the discrete first order differential operator in `y`-direction on a grid of size `N` use
>>> Dy = fidop2d(N,'y')
Notes
-----
For clarity here some comments on the rationale behind the code.
*The Laplacian*
If `Dxf` and `Dyf` are computed using forward differences, i.e.
>>> Dxf,Dyf = fidop2d(N,'xy','f')
then the discrete Laplacian `Lh` based on centered differences is
obtained by
>>> Lh = -(Dxf.T*Dxf + Dyf.T*Dyf)
or analogously for backward differences,
>>> Dxb,Dyb = fidop2d(N,'xy','b')
then
>>> Lh = Dxb.T*Dxb + Dyb.T*Dyb
*The Divergence*
Note adjoints: for :math:`p` in
:math:`H_0(\\mathrm{div}):=\\{p \\in L^2(\\Omega)` | div :math:`(p) \\in L^2(\\Omega), pn = 0`
on the boundary of :math:`\\Omega\\}` we have
.. math::
\\begin{align}
\\int_{\\Omega} \\nabla u\\cdot p dx &= \\int_{\\Omega} u p \\cdot n dS - \\int_{\\Omega} u \\textrm{ div }(p) dx\\\\
& = 0 -\\int_{\\Omega} u \\textrm{ div }(p) dx
\\end{align}
or in short :math:`\\textrm{div}^\\ast = -\\nabla`. Hence for a spatial discretization take
`Div_h ~ -Dh.T` as approximation for the divergence.
"""
# Sanity checks for `N`
if not np.isscalar(N) or not plt.is_numlike(N) or not np.isreal(N).all():
raise TypeError("Input `N` must be a real scalar!")
if not np.isfinite(N):
raise TypeError("Input `N` must be finite!")
if round(N) != N:
raise TypeError("`N` has to be a positive integer!")
if N <= 1:
raise ValueError("`N` has to be greater than 1!")
# Sanity checks for `drt`
if not isinstance(drt,(str,unicode)):
raise TypeError("Input `drt` has to be a string!")
if drt != 'x' and drt != 'y' and drt != 'xy':
raise ValueError("Input `drt` has to be either 'x', 'y' or 'xy'")
elif drt == 'x' or drt == 'y':
myout = 1
else:
myout = 2
# Sanity checks for `fds`
if not isinstance(fds,(str,unicode)):
raise TypeError("Input `fds` has to be a string!")
if fds != 'c' and fds != 'b' and fds != 'f':
raise ValueError("Input `fds` has to be either 'c' (centered), 'b' (backward) or 'f' (forward)")
# Initialize vector of ones needed to build matrices
e = np.ones((1,N));
# Building blocks for the operators
if fds=='c':
# Centered differences
A = spdiags(np.r_[-e,e],np.array([-1,1]),N,N,format='lil');
A[0,0] = -2.0; A[0,1] = 2.0; A[N-1,N-2] = -2.0; A[N-1,N-1] = 2.0;
A = 0.5*A;
A = A.tocsr()
elif fds=='f':
# Forward differences
A = spdiags(np.r_[-e,e],np.array([0,1]),N,N,format='lil');
A[N-1,:] = 0.0;
A = A.tocsr()
else:
# Backward differences
A = spdiags(np.r_[-e,e],np.array([-1,0]),N,N,format='lil');
A[0,:] = 0.0;
A = A.tocsr()
# Second building block needed: the identity
IN = eye(N,N,format='csr');
# Compute output matrix/matrices
if myout==1:
# Only one output: determine direction
if drt=='x':
# x-direction
return kron(IN,A,format='csr')
else:
# y-direction
return kron(A,IN,format='csr')
else:
# Two outputs: order of directions is always d/dx,d/dy]
return kron(IN,A,format='csr'), kron(A,IN,format='csr')
##########################################################################################
def myff2n(n):
"""
Give factor settings for a two-level full factorial design
Parameters
----------
n : int
Number of factors
Returns
-------
dff2 : NumPy 2darray
An `m`-by-`n` array of factor settings
Examples
--------
>>> dFF2 = ff2n(3)
>>> dFF2
array([[ 0., 0., 0.],
[ 0., 0., 1.],
[ 0., 1., 0.],
[ 0., 1., 1.],
[ 1., 0., 0.],
[ 1., 0., 1.],
[ 1., 1., 0.],
[ 1., 1., 1.]])
"""
# Check correctness of input
if not np.isscalar(n) or not plt.is_numlike(n) or not np.isreal(n).all():
raise TypeError("Input `n` must be a real scalar!")
if not np.isfinite(n):
raise TypeError("Input `n` must be finite!")
if round(n) != n:
raise TypeError("`n` has to be a positive integer!")
if n <= 0:
raise ValueError("`n` has to be greater than 0!")
# Output array dff2 has 2^n rows
rows = 2**n
ncycles = rows
dff2 = np.zeros((rows,n))
# This is adapted from the MATLAB source file ff2n.m
for k in xrange(0,n):
settings = np.arange(0,2)
settings.shape = (1,2)
ncycles = ncycles/2.0
nreps = rows/(2*ncycles)
settings = settings[np.zeros((1,nreps)).astype(int),:]
settings = settings.flatten(1)
settings.shape = (settings.size,1)
settings = settings[:,np.zeros((1,ncycles)).astype(int)]
dff2[:,n-k-1] = settings.flatten(1)
return dff2