surfacetopography 0.91.2__py3.6-linux-x86_64.egg
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- EGG-INFO/PKG-INFO +11 -0
- EGG-INFO/SOURCES.txt +96 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/native_libs.txt +1 -0
- EGG-INFO/not-zip-safe +1 -0
- EGG-INFO/requires.txt +6 -0
- EGG-INFO/top_level.txt +3 -0
- SurfaceTopography/ChangeLog.md +358 -0
- SurfaceTopography/Converters.py +197 -0
- SurfaceTopography/Generation.py +440 -0
- SurfaceTopography/HeightContainer.py +315 -0
- SurfaceTopography/IO/DI.py +261 -0
- SurfaceTopography/IO/FromFile.py +802 -0
- SurfaceTopography/IO/H5.py +79 -0
- SurfaceTopography/IO/IBW.py +176 -0
- SurfaceTopography/IO/MI.py +307 -0
- SurfaceTopography/IO/Matlab.py +106 -0
- SurfaceTopography/IO/NC.py +228 -0
- SurfaceTopography/IO/NPY.py +139 -0
- SurfaceTopography/IO/OPDx.py +885 -0
- SurfaceTopography/IO/Reader.py +389 -0
- SurfaceTopography/IO/__init__.py +241 -0
- SurfaceTopography/IO/__pycache__/DI.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/FromFile.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/H5.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/IBW.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/MI.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/Matlab.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/NC.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/NPY.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/OPDx.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/Reader.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Interpolation.py +30 -0
- SurfaceTopography/Nonuniform/Autocorrelation.py +161 -0
- SurfaceTopography/Nonuniform/Detrending.py +87 -0
- SurfaceTopography/Nonuniform/PowerSpectrum.py +178 -0
- SurfaceTopography/Nonuniform/ScalarParameters.py +140 -0
- SurfaceTopography/Nonuniform/VariableBandwidth.py +155 -0
- SurfaceTopography/Nonuniform/__init__.py +28 -0
- SurfaceTopography/Nonuniform/__pycache__/Autocorrelation.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/Detrending.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/PowerSpectrum.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/ScalarParameters.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/VariableBandwidth.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/common.py +95 -0
- SurfaceTopography/NonuniformLineScan.py +310 -0
- SurfaceTopography/Special.py +225 -0
- SurfaceTopography/Tools/__init__.py +28 -0
- SurfaceTopography/Tools/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Tools/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/Tools/common.py +75 -0
- SurfaceTopography/Uniform/Autocorrelation.py +211 -0
- SurfaceTopography/Uniform/Detrending.py +155 -0
- SurfaceTopography/Uniform/Filtering.py +226 -0
- SurfaceTopography/Uniform/Interpolation.py +159 -0
- SurfaceTopography/Uniform/PowerSpectrum.py +197 -0
- SurfaceTopography/Uniform/ScalarParameters.py +173 -0
- SurfaceTopography/Uniform/VariableBandwidth.py +169 -0
- SurfaceTopography/Uniform/__init__.py +28 -0
- SurfaceTopography/Uniform/__pycache__/Autocorrelation.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/Detrending.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/Filtering.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/Interpolation.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/PowerSpectrum.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/ScalarParameters.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/VariableBandwidth.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/common.py +260 -0
- SurfaceTopography/UniformLineScanAndTopography.py +887 -0
- SurfaceTopography/__init__.py +62 -0
- SurfaceTopography/__pycache__/Converters.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/Generation.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/HeightContainer.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/Interpolation.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/NonuniformLineScan.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/Special.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/UniformLineScanAndTopography.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/common.py +111 -0
- _SurfaceTopography.cpython-36m-x86_64-linux-gnu.so +0 -0
- _SurfaceTopography.py +8 -0
- __pycache__/_SurfaceTopography.cpython-36.pyc +0 -0
- test/IO/__init__.py +28 -0
- test/IO/__pycache__/__init__.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_ibw.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_io.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_mi.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_netcdf.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_npy.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_opdx.cpython-36.pyc +0 -0
- test/IO/test_ibw.py +178 -0
- test/IO/test_io.py +401 -0
- test/IO/test_mi.py +103 -0
- test/IO/test_netcdf.py +134 -0
- test/IO/test_npy.py +135 -0
- test/IO/test_opdx.py +441 -0
- test/__init__.py +26 -0
- test/__pycache__/__init__.cpython-36.pyc +0 -0
- test/__pycache__/compare-orientation-with-gwyddion.cpython-36.pyc +0 -0
- test/__pycache__/conftest.cpython-36.pyc +0 -0
- test/__pycache__/test_autocorrelation.cpython-36.pyc +0 -0
- test/__pycache__/test_bicubic.cpython-36.pyc +0 -0
- test/__pycache__/test_filtering.cpython-36.pyc +0 -0
- test/__pycache__/test_generation.cpython-36.pyc +0 -0
- test/__pycache__/test_import.cpython-36.pyc +0 -0
- test/__pycache__/test_make_sphere.cpython-36.pyc +0 -0
- test/__pycache__/test_power_spectrum.cpython-36.pyc +0 -0
- test/__pycache__/test_scalar_parameters.cpython-36.pyc +0 -0
- test/__pycache__/test_topography1.cpython-36.pyc +0 -0
- test/__pycache__/test_topography2.cpython-36.pyc +0 -0
- test/__pycache__/test_variable_bandwidth.cpython-36.pyc +0 -0
- test/compare-orientation-with-gwyddion.py +102 -0
- test/conftest.py +80 -0
- test/test_autocorrelation.py +311 -0
- test/test_bicubic.py +190 -0
- test/test_filtering.py +69 -0
- test/test_generation.py +259 -0
- test/test_import.py +54 -0
- test/test_make_sphere.py +108 -0
- test/test_power_spectrum.py +259 -0
- test/test_scalar_parameters.py +176 -0
- test/test_topography1.py +1287 -0
- test/test_topography2.py +328 -0
- test/test_variable_bandwidth.py +107 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2015-2016, 2018, 2020 Lars Pastewka
|
|
3
|
+
# 2018, 2020 Antoine Sanner
|
|
4
|
+
# 2015-2016 Till Junge
|
|
5
|
+
#
|
|
6
|
+
# ### MIT license
|
|
7
|
+
#
|
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
# furnished to do so, subject to the following conditions:
|
|
14
|
+
#
|
|
15
|
+
# The above copyright notice and this permission notice shall be included in
|
|
16
|
+
# all copies or substantial portions of the Software.
|
|
17
|
+
#
|
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
# SOFTWARE.
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Helper functions for the generation of random fractal surfaces
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import numpy as np
|
|
32
|
+
from . import Topography, UniformLineScan
|
|
33
|
+
from .Tools.common import compute_wavevectors, ifftn
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CapillaryWavesExact(object):
|
|
37
|
+
"""Frozen capillary waves"""
|
|
38
|
+
Error = Exception
|
|
39
|
+
|
|
40
|
+
def __init__(self, nb_grid_pts, physical_sizes, mass_density,
|
|
41
|
+
surface_tension,
|
|
42
|
+
bending_stiffness, seed=None):
|
|
43
|
+
"""
|
|
44
|
+
Generates a surface with an exact power spectrum (deterministic
|
|
45
|
+
amplitude)
|
|
46
|
+
Keyword Arguments:
|
|
47
|
+
nb_grid_pts -- Tuple containing number of points in spatial
|
|
48
|
+
directions. The length of the tuple determines the
|
|
49
|
+
spatial dimension of the problem (for the time
|
|
50
|
+
being, only 1D or square 2D)
|
|
51
|
+
physical_sizes -- domain physical_sizes. For multidimensional
|
|
52
|
+
problems, a tuple can be provided to specify the
|
|
53
|
+
length per dimension. If the tuple has less
|
|
54
|
+
entries than dimensions, the last value in
|
|
55
|
+
repeated.
|
|
56
|
+
mass_density -- Mass density
|
|
57
|
+
surface_tension -- SurfaceTopography tension
|
|
58
|
+
bending_stiffness -- Bending stiffness
|
|
59
|
+
rms_height -- root mean square height of surface
|
|
60
|
+
rms_slope -- root mean square slope of surface
|
|
61
|
+
seed -- (default hash(None)) for repeatability, the random
|
|
62
|
+
number generator is seeded previous to outputting
|
|
63
|
+
the generated surface
|
|
64
|
+
"""
|
|
65
|
+
if seed is not None:
|
|
66
|
+
np.random.seed(hash(seed))
|
|
67
|
+
if not hasattr(nb_grid_pts, "__iter__"):
|
|
68
|
+
nb_grid_pts = (nb_grid_pts,)
|
|
69
|
+
if not hasattr(physical_sizes, "__iter__"):
|
|
70
|
+
physical_sizes = (physical_sizes,)
|
|
71
|
+
|
|
72
|
+
self.dim = len(nb_grid_pts)
|
|
73
|
+
if self.dim not in (1, 2):
|
|
74
|
+
raise self.Error(
|
|
75
|
+
"Dimension of this problem is {}. Only 1 and 2-dimensional "
|
|
76
|
+
"problems are supported".format(self.dim))
|
|
77
|
+
self.nb_grid_pts = nb_grid_pts
|
|
78
|
+
tmpsize = list()
|
|
79
|
+
for i in range(self.dim):
|
|
80
|
+
tmpsize.append(physical_sizes[min(i, len(physical_sizes) - 1)])
|
|
81
|
+
self.size = tuple(tmpsize)
|
|
82
|
+
|
|
83
|
+
self.mass_density = mass_density
|
|
84
|
+
self.surface_tension = surface_tension
|
|
85
|
+
self.bending_stiffness = bending_stiffness
|
|
86
|
+
|
|
87
|
+
self.q = compute_wavevectors( # pylint: disable=invalid-name
|
|
88
|
+
self.nb_grid_pts, self.size, self.dim)
|
|
89
|
+
self.coeffs = self.generate_phases()
|
|
90
|
+
self.generate_amplitudes()
|
|
91
|
+
self.distribution = self.amplitude_distribution()
|
|
92
|
+
self.active_coeffs = None
|
|
93
|
+
|
|
94
|
+
def get_negative_frequency_iterator(self):
|
|
95
|
+
" frequency complement"
|
|
96
|
+
|
|
97
|
+
def iterator(): # pylint: disable=missing-docstring
|
|
98
|
+
for i in range(self.nb_grid_pts[0]):
|
|
99
|
+
for j in range(self.nb_grid_pts[1] // 2 + 1):
|
|
100
|
+
yield (i, j), (-i, -j)
|
|
101
|
+
|
|
102
|
+
return iterator()
|
|
103
|
+
|
|
104
|
+
def amplitude_distribution(self): # pylint: disable=no-self-use
|
|
105
|
+
"""
|
|
106
|
+
returns a multiplicative factor to apply to the fourier coeffs before
|
|
107
|
+
computing the inverse transform (trivial in this case, since there's no
|
|
108
|
+
stochastic distro in this case)
|
|
109
|
+
"""
|
|
110
|
+
return 1.
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def abs_q(self):
|
|
114
|
+
" radial distances in q-space"
|
|
115
|
+
q_norm = np.sqrt((self.q[0] ** 2).reshape((-1, 1)) + self.q[1] ** 2)
|
|
116
|
+
order = np.argsort(q_norm, axis=None)
|
|
117
|
+
# The first entry (for |q| = 0) is rejected, since it's 0 by construct
|
|
118
|
+
return q_norm.flatten()[order][1:]
|
|
119
|
+
|
|
120
|
+
def generate_phases(self):
|
|
121
|
+
"""
|
|
122
|
+
generates appropriate random phases (φ(-q) = -φ(q))
|
|
123
|
+
"""
|
|
124
|
+
rand_phase = np.random.rand(*self.nb_grid_pts) * 2 * np.pi
|
|
125
|
+
coeffs = np.exp(1j * rand_phase)
|
|
126
|
+
for pos_it, neg_it in self.get_negative_frequency_iterator():
|
|
127
|
+
if pos_it != (0, 0):
|
|
128
|
+
coeffs[neg_it] = coeffs[pos_it].conj()
|
|
129
|
+
if self.nb_grid_pts[0] % 2 == 0:
|
|
130
|
+
r_2 = self.nb_grid_pts[0] // 2
|
|
131
|
+
coeffs[r_2, 0] = coeffs[r_2, r_2] = coeffs[0, r_2] = 1
|
|
132
|
+
return coeffs
|
|
133
|
+
|
|
134
|
+
def generate_amplitudes(self):
|
|
135
|
+
"compute an amplitude distribution"
|
|
136
|
+
q_2 = self.q[0].reshape(-1, 1) ** 2 + self.q[1] ** 2
|
|
137
|
+
q_2[0, 0] = 1 # to avoid div by zeros, needs to be fixed after
|
|
138
|
+
self.coeffs *= 1 / (self.mass_density + self.surface_tension * q_2 +
|
|
139
|
+
self.bending_stiffness * q_2 * q_2)
|
|
140
|
+
self.coeffs[0, 0] = 0 # et voilà
|
|
141
|
+
# Fix Shannon limit:
|
|
142
|
+
# self.coeffs[q_2 > self.q_max**2] = 0
|
|
143
|
+
|
|
144
|
+
def get_topography(self):
|
|
145
|
+
"""
|
|
146
|
+
Computes and returs a NumpySurface object with the specified
|
|
147
|
+
properties. This follows appendices A and B of Persson et al. (2005)
|
|
148
|
+
|
|
149
|
+
Persson et al., On the nature of surface roughness with application to
|
|
150
|
+
contact mechanics, sealing, rubber friction and adhesion, J. Phys.:
|
|
151
|
+
Condens. Matter 17 (2005) R1-R62, http://arxiv.org/abs/cond-mat/0502419
|
|
152
|
+
|
|
153
|
+
Keyword Arguments:
|
|
154
|
+
lambda_max -- (default None) specifies a cutoff value for the longest
|
|
155
|
+
wavelength. By default, this is the domain physical_sizes
|
|
156
|
+
in the smallest dimension
|
|
157
|
+
lambda_min -- (default None) specifies a cutoff value for the shortest
|
|
158
|
+
wavelength. by default this is determined by Shannon's
|
|
159
|
+
Theorem.
|
|
160
|
+
"""
|
|
161
|
+
active_coeffs = self.coeffs.copy()
|
|
162
|
+
active_coeffs *= self.distribution
|
|
163
|
+
area = np.prod(self.size)
|
|
164
|
+
profile = ifftn(active_coeffs, area).real
|
|
165
|
+
self.active_coeffs = active_coeffs
|
|
166
|
+
return Topography(profile, self.size)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
###
|
|
170
|
+
|
|
171
|
+
def _irfft2(karr, rarr, progress_callback=None):
|
|
172
|
+
"""
|
|
173
|
+
Inverse 2d real-to-complex FFT with callback that allows to report status
|
|
174
|
+
of the computation to user.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
karr : array_like
|
|
179
|
+
Fourier-space representation
|
|
180
|
+
rarr : array_like
|
|
181
|
+
Real-space representation
|
|
182
|
+
progress_callback : function(i, n)
|
|
183
|
+
Function that is called to report progress.
|
|
184
|
+
"""
|
|
185
|
+
nrows, ncolumns = karr.shape
|
|
186
|
+
for i in range(ncolumns):
|
|
187
|
+
if progress_callback is not None:
|
|
188
|
+
progress_callback(i, ncolumns + nrows - 1)
|
|
189
|
+
karr[:, i] = np.fft.ifft(karr[:, i])
|
|
190
|
+
for i in range(nrows):
|
|
191
|
+
if progress_callback is not None:
|
|
192
|
+
progress_callback(i + ncolumns, ncolumns + nrows - 1)
|
|
193
|
+
if rarr.shape[1] % 2 == 0:
|
|
194
|
+
rarr[i, :] = np.fft.irfft(karr[i, :])
|
|
195
|
+
else:
|
|
196
|
+
rarr[i, :] = np.fft.irfft(karr[i, :], n=rarr.shape[1])
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def self_affine_prefactor(nb_grid_pts, physical_sizes, Hurst, rms_height=None,
|
|
200
|
+
rms_slope=None, short_cutoff=None, long_cutoff=None):
|
|
201
|
+
r"""
|
|
202
|
+
Compute prefactor :math:`C_0` for the power-spectrum density of an ideal
|
|
203
|
+
self-affine topography given by
|
|
204
|
+
|
|
205
|
+
.. math ::
|
|
206
|
+
|
|
207
|
+
C(q) = C_0 q^{-2-2H}
|
|
208
|
+
|
|
209
|
+
for two-dimensional topography maps and
|
|
210
|
+
|
|
211
|
+
.. math ::
|
|
212
|
+
|
|
213
|
+
C(q) = C_0 q^{-1-2H}
|
|
214
|
+
|
|
215
|
+
for one-dimensional line scans. Here :math:`H` is the Hurst exponent.
|
|
216
|
+
|
|
217
|
+
Note:
|
|
218
|
+
In the 2D case:
|
|
219
|
+
|
|
220
|
+
.. math ::
|
|
221
|
+
|
|
222
|
+
h^2_{rms} = \frac{1}{2 \pi} \int_{0}^{\infty} q C^{iso}(q) dq
|
|
223
|
+
|
|
224
|
+
whereas in the 1D case:
|
|
225
|
+
|
|
226
|
+
.. math ::
|
|
227
|
+
|
|
228
|
+
h^2_{rms} = \frac{1}{\pi} \int_{0}^{\infty} C^{1D}(q) dq
|
|
229
|
+
|
|
230
|
+
See Equations (1) and (4) in [1].
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
nb_grid_pts : array_like
|
|
236
|
+
Resolution of the topography map or the line scan.
|
|
237
|
+
physical_sizes : array_like
|
|
238
|
+
Physical physical_sizes of the topography map or the line scan.
|
|
239
|
+
Hurst : float
|
|
240
|
+
Hurst exponent.
|
|
241
|
+
rms_height : float
|
|
242
|
+
Root mean-squared height.
|
|
243
|
+
rms_slope : float
|
|
244
|
+
Root mean-squared slope of the topography map or the line scan.
|
|
245
|
+
short_cutoff : float
|
|
246
|
+
Short-wavelength cutoff.
|
|
247
|
+
long_cutoff : float
|
|
248
|
+
Long-wavelength cutoff.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
prefactor : float
|
|
253
|
+
Prefactor :math:`\sqrt{C_0}`
|
|
254
|
+
|
|
255
|
+
References
|
|
256
|
+
-----------
|
|
257
|
+
[1]: Jacobs, Junge, Pastewka,
|
|
258
|
+
Surf. Topgogr.: Metrol. Prop. 5, 013001 (2017)
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
nb_grid_pts = np.asarray(nb_grid_pts)
|
|
262
|
+
physical_sizes = np.asarray(physical_sizes)
|
|
263
|
+
|
|
264
|
+
if short_cutoff is not None:
|
|
265
|
+
q_max = 2 * np.pi / short_cutoff
|
|
266
|
+
else:
|
|
267
|
+
q_max = np.pi * np.min(nb_grid_pts / physical_sizes)
|
|
268
|
+
|
|
269
|
+
if long_cutoff is not None:
|
|
270
|
+
q_min = 2 * np.pi / long_cutoff
|
|
271
|
+
else:
|
|
272
|
+
q_min = 2 * np.pi * np.max(1 / physical_sizes)
|
|
273
|
+
|
|
274
|
+
area = np.prod(physical_sizes)
|
|
275
|
+
|
|
276
|
+
if rms_height is not None:
|
|
277
|
+
# Assuming no rolloff region
|
|
278
|
+
fac = 2 * rms_height / np.sqrt(q_min ** (-2 * Hurst) -
|
|
279
|
+
q_max ** (-2 * Hurst)) * np.sqrt(
|
|
280
|
+
Hurst * np.pi)
|
|
281
|
+
elif rms_slope is not None:
|
|
282
|
+
fac = 2 * rms_slope / np.sqrt(q_max ** (2 - 2 * Hurst) -
|
|
283
|
+
q_min ** (2 - 2 * Hurst)) * np.sqrt(
|
|
284
|
+
(1 - Hurst) * np.pi)
|
|
285
|
+
else:
|
|
286
|
+
raise ValueError('Neither rms height nor rms slope is defined!')
|
|
287
|
+
|
|
288
|
+
if len(nb_grid_pts) == 1:
|
|
289
|
+
fac /= np.sqrt(2)
|
|
290
|
+
|
|
291
|
+
return fac * np.prod(nb_grid_pts) / np.sqrt(area)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def fourier_synthesis(nb_grid_pts, physical_sizes, hurst,
|
|
295
|
+
rms_height=None, rms_slope=None, c0=None,
|
|
296
|
+
short_cutoff=None, long_cutoff=None, rolloff=1.0,
|
|
297
|
+
amplitude_distribution=lambda n: np.random.normal(
|
|
298
|
+
size=n),
|
|
299
|
+
periodic=True, rfn=None, kfn=None,
|
|
300
|
+
progress_callback=None):
|
|
301
|
+
r"""
|
|
302
|
+
Create a self-affine, randomly rough surface using a Fourier filtering
|
|
303
|
+
algorithm. The algorithm is described in:
|
|
304
|
+
Ramisetti et al., J. Phys.: Condens. Matter 23, 215004 (2011);
|
|
305
|
+
Jacobs, Junge, Pastewka, Surf. Topgogr.: Metrol. Prop. 5, 013001 (2017)
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
nb_grid_pts : array_like
|
|
310
|
+
Resolution of the topography map.
|
|
311
|
+
physical_sizes : array_like
|
|
312
|
+
Physical physical_sizes of the topography map.
|
|
313
|
+
hurst : float
|
|
314
|
+
Hurst exponent.
|
|
315
|
+
rms_height : float
|
|
316
|
+
Root mean-squared height.
|
|
317
|
+
rms_slope : float
|
|
318
|
+
Root mean-squared slope.
|
|
319
|
+
c0: float
|
|
320
|
+
self affine prefactor :math:`C_0`:
|
|
321
|
+
:math:`C(q) = C_0 q^{-2-2H}`
|
|
322
|
+
short_cutoff : float
|
|
323
|
+
Short-wavelength cutoff.
|
|
324
|
+
long_cutoff : float
|
|
325
|
+
Long-wavelength cutoff.
|
|
326
|
+
rolloff : float
|
|
327
|
+
Value for the power-spectral density (PSD) below the long-wavelength
|
|
328
|
+
cutoff. This multiplies the value at the cutoff, i.e. unit will give a
|
|
329
|
+
PSD that is flat below the cutoff, zero will give a PSD that is
|
|
330
|
+
vanishes below cutoff. (Default: 1.0)
|
|
331
|
+
amplitude_distribution : function
|
|
332
|
+
Function that generates the distribution of amplitudes.
|
|
333
|
+
(Default: np.random.normal)
|
|
334
|
+
periodic : bool
|
|
335
|
+
Return a periodic surface. (Default: True)
|
|
336
|
+
rfn : str
|
|
337
|
+
Name of file that stores the real-space array. If specified, real-space
|
|
338
|
+
array will be created as a memory mapped file. This is useful for
|
|
339
|
+
creating very large topography maps. (Default: None)
|
|
340
|
+
kfn : str
|
|
341
|
+
Name of file that stores the Fourie-space array. If specified,
|
|
342
|
+
real-space array will be created as a memory mapped file. This is
|
|
343
|
+
useful for creating very large topography maps. (Default: None)
|
|
344
|
+
progress_callback : function(i, n)
|
|
345
|
+
Function that is called to report progress.
|
|
346
|
+
|
|
347
|
+
Returns
|
|
348
|
+
-------
|
|
349
|
+
topography : UniformTopography or UniformLineScan
|
|
350
|
+
The topography.
|
|
351
|
+
"""
|
|
352
|
+
if short_cutoff is not None:
|
|
353
|
+
q_max = 2 * np.pi / short_cutoff
|
|
354
|
+
else:
|
|
355
|
+
q_max = np.pi * np.min(
|
|
356
|
+
np.asarray(nb_grid_pts) / np.asarray(physical_sizes))
|
|
357
|
+
|
|
358
|
+
if long_cutoff is not None:
|
|
359
|
+
q_min = 2 * np.pi / long_cutoff
|
|
360
|
+
else:
|
|
361
|
+
q_min = None
|
|
362
|
+
|
|
363
|
+
if c0 is None:
|
|
364
|
+
fac = self_affine_prefactor(nb_grid_pts, physical_sizes, hurst,
|
|
365
|
+
rms_height=rms_height,
|
|
366
|
+
rms_slope=rms_slope,
|
|
367
|
+
short_cutoff=short_cutoff,
|
|
368
|
+
long_cutoff=long_cutoff)
|
|
369
|
+
else:
|
|
370
|
+
# prefactor for the fourier heights
|
|
371
|
+
fac = np.sqrt(c0) * np.prod(nb_grid_pts) / np.sqrt(
|
|
372
|
+
np.prod(physical_sizes))
|
|
373
|
+
# C(q) = c0 q^(-2-2H) = 1 / A |fh(q)|^2
|
|
374
|
+
# and h(x,y) = sum(1/A fh(q) e^(iqx)))
|
|
375
|
+
# compensate for the np.fft normalisation
|
|
376
|
+
|
|
377
|
+
if len(nb_grid_pts) == 2:
|
|
378
|
+
nx, ny = nb_grid_pts
|
|
379
|
+
sx, sy = physical_sizes
|
|
380
|
+
kny = ny // 2 + 1
|
|
381
|
+
kshape = (nx, kny)
|
|
382
|
+
else:
|
|
383
|
+
nx = 1
|
|
384
|
+
ny, = nb_grid_pts
|
|
385
|
+
sx = 1
|
|
386
|
+
sy, = physical_sizes
|
|
387
|
+
kny = ny // 2 + 1
|
|
388
|
+
kshape = (kny,)
|
|
389
|
+
|
|
390
|
+
# Create in-memory or memory-mapped arrays as storage buffers
|
|
391
|
+
if rfn is None:
|
|
392
|
+
rarr = np.empty(nb_grid_pts, dtype=np.float64)
|
|
393
|
+
else:
|
|
394
|
+
rarr = np.memmap(rfn, np.float64, 'w+', shape=nb_grid_pts)
|
|
395
|
+
if kfn is None:
|
|
396
|
+
karr = np.empty(kshape, dtype=np.complex128)
|
|
397
|
+
else:
|
|
398
|
+
karr = np.memmap(kfn, np.complex128, 'w+', shape=kshape)
|
|
399
|
+
|
|
400
|
+
qy = 2 * np.pi * np.arange(kny) / sy
|
|
401
|
+
for x in range(nx):
|
|
402
|
+
if progress_callback is not None:
|
|
403
|
+
progress_callback(x, nx - 1)
|
|
404
|
+
if x > nx // 2:
|
|
405
|
+
qx = 2 * np.pi * (nx - x) / sx
|
|
406
|
+
else:
|
|
407
|
+
qx = 2 * np.pi * x / sx
|
|
408
|
+
q_sq = qx ** 2 + qy ** 2
|
|
409
|
+
if x == 0:
|
|
410
|
+
q_sq[0] = 1.
|
|
411
|
+
phase = np.exp(2 * np.pi * np.random.rand(kny) * 1j)
|
|
412
|
+
ran = fac * phase * amplitude_distribution(kny)
|
|
413
|
+
if len(nb_grid_pts) == 2:
|
|
414
|
+
karr[x, :] = ran * q_sq ** (-(1 + hurst) / 2)
|
|
415
|
+
karr[x, q_sq > q_max ** 2] = 0.
|
|
416
|
+
else:
|
|
417
|
+
karr[:] = ran * q_sq ** (-(0.5 + hurst) / 2)
|
|
418
|
+
karr[q_sq > q_max ** 2] = 0.
|
|
419
|
+
if q_min is not None:
|
|
420
|
+
mask = q_sq < q_min ** 2
|
|
421
|
+
if len(nb_grid_pts) == 2:
|
|
422
|
+
karr[x, mask] = rolloff * ran[mask] * q_min ** (-(1 + hurst))
|
|
423
|
+
else:
|
|
424
|
+
karr[mask] = rolloff * ran[mask] * q_min ** (-(0.5 + hurst))
|
|
425
|
+
if len(nb_grid_pts) == 2:
|
|
426
|
+
for iy in [0, -1] if ny % 2 == 0 else [0]:
|
|
427
|
+
# Enforce symmetry
|
|
428
|
+
if nx % 2 == 0:
|
|
429
|
+
karr[0, iy] = np.real(karr[0, iy])
|
|
430
|
+
karr[nx // 2, iy] = np.real(karr[nx // 2, iy])
|
|
431
|
+
karr[1:nx // 2, iy] = karr[-1:nx // 2:-1, iy].conj()
|
|
432
|
+
else:
|
|
433
|
+
karr[0, iy] = np.real(karr[0, iy])
|
|
434
|
+
karr[1:nx // 2 + 1, iy] = karr[-1:nx // 2:-1, iy].conj()
|
|
435
|
+
_irfft2(karr, rarr, progress_callback)
|
|
436
|
+
return Topography(rarr, physical_sizes, periodic=periodic)
|
|
437
|
+
else:
|
|
438
|
+
karr[0] = np.real(karr[0])
|
|
439
|
+
rarr[:] = np.fft.irfft(karr)
|
|
440
|
+
return UniformLineScan(rarr, sy, periodic=periodic)
|