rfgen 0.1.0__tar.gz
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.
- rfgen-0.1.0/LICENSE +30 -0
- rfgen-0.1.0/MANIFEST.in +24 -0
- rfgen-0.1.0/PKG-INFO +167 -0
- rfgen-0.1.0/README.md +129 -0
- rfgen-0.1.0/examples/analysis_example.py +218 -0
- rfgen-0.1.0/examples/generation_example.py +129 -0
- rfgen-0.1.0/pyproject.toml +87 -0
- rfgen-0.1.0/setup.cfg +4 -0
- rfgen-0.1.0/src/rfgen/__init__.py +98 -0
- rfgen-0.1.0/src/rfgen/analysis/__init__.py +56 -0
- rfgen-0.1.0/src/rfgen/analysis/autocorrelation.py +230 -0
- rfgen-0.1.0/src/rfgen/analysis/moments.py +330 -0
- rfgen-0.1.0/src/rfgen/analysis/spectrum.py +340 -0
- rfgen-0.1.0/src/rfgen/generators/__init__.py +22 -0
- rfgen-0.1.0/src/rfgen/generators/matern.py +283 -0
- rfgen-0.1.0/src/rfgen/generators/selfaffine.py +221 -0
- rfgen-0.1.0/src/rfgen/py.typed +0 -0
- rfgen-0.1.0/src/rfgen.egg-info/PKG-INFO +167 -0
- rfgen-0.1.0/src/rfgen.egg-info/SOURCES.txt +23 -0
- rfgen-0.1.0/src/rfgen.egg-info/dependency_links.txt +1 -0
- rfgen-0.1.0/src/rfgen.egg-info/requires.txt +13 -0
- rfgen-0.1.0/src/rfgen.egg-info/top_level.txt +1 -0
- rfgen-0.1.0/tests/__init__.py +2 -0
- rfgen-0.1.0/tests/test_analysis.py +150 -0
- rfgen-0.1.0/tests/test_generators.py +171 -0
rfgen-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-2025, Vladislav A. Yastrebov
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
rfgen-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Include license and readme
|
|
2
|
+
include LICENSE
|
|
3
|
+
include README.md
|
|
4
|
+
|
|
5
|
+
# Include pyproject.toml
|
|
6
|
+
include pyproject.toml
|
|
7
|
+
|
|
8
|
+
# Include all source files
|
|
9
|
+
recursive-include src/rfgen *.py
|
|
10
|
+
recursive-include src/rfgen py.typed
|
|
11
|
+
|
|
12
|
+
# Include tests
|
|
13
|
+
recursive-include tests *.py
|
|
14
|
+
|
|
15
|
+
# Include examples
|
|
16
|
+
recursive-include examples *.py
|
|
17
|
+
|
|
18
|
+
# Exclude compiled files
|
|
19
|
+
global-exclude __pycache__
|
|
20
|
+
global-exclude *.py[cod]
|
|
21
|
+
global-exclude *.so
|
|
22
|
+
global-exclude .DS_Store
|
|
23
|
+
global-exclude *.egg-info
|
|
24
|
+
|
rfgen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rfgen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Periodic Gaussian random field generation using spectral filtering techniques
|
|
5
|
+
Author-email: Vladislav Yastrebov <vladislav.yastrebov@minesparis.psl.eu>
|
|
6
|
+
Maintainer-email: Vladislav Yastrebov <vladislav.yastrebov@minesparis.psl.eu>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/vyastreb/rfgen
|
|
9
|
+
Project-URL: Repository, https://github.com/vyastreb/rfgen
|
|
10
|
+
Project-URL: Documentation, https://github.com/vyastreb/rfgen#readme
|
|
11
|
+
Project-URL: Issues, https://github.com/vyastreb/rfgen/issues
|
|
12
|
+
Keywords: random field,Gaussian random field,self-affine,Matérn covariance,spectral methods,Fourier filtering,rough surfaces,stochastic fields
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy>=1.20.0
|
|
28
|
+
Requires-Dist: scipy>=1.7.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
|
+
Provides-Extra: plot
|
|
34
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "plot"
|
|
35
|
+
Provides-Extra: all
|
|
36
|
+
Requires-Dist: rfgen[dev,plot]; extra == "all"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Generator of a periodic random self-affine field
|
|
40
|
+
|
|
41
|
+
Generate **periodic** 1D/2D/3D self‑affine Gaussian random fields with a prescribed power‑law spectrum in Fourier space. Includes two variants: a "noisy" spectrum (filtered white noise) and an **exact‑magnitude** spectrum with random phases only. C++ and Python implementations.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Project information
|
|
46
|
+
|
|
47
|
+
+ Author: Vladislav A. Yastrebov
|
|
48
|
+
+ Affiliation: CNRS, Mines Paris, Centre des Materiaux, Versailles, Paris
|
|
49
|
+
+ Date: 2015-2025
|
|
50
|
+
+ Licence: BSD 3-Clause
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Description & references
|
|
55
|
+
|
|
56
|
+
This repository implements (in C++ and Python) the Fourier‑space filtering approach introduced by:
|
|
57
|
+
|
|
58
|
+
- **Hu, Y.Z.; Tonder, K.** Simulation of 3‑D random rough surface by 2‑D digital filter in Fourier space. *Int. J. Mach. Tools Manufact.* **32** (1992) 83–90. DOI: [10.1016/0890-6955(92)90064-N](https://doi.org/10.1016/0890-6955%2892%2990064-N)
|
|
59
|
+
|
|
60
|
+
It has been used, e.g., in:
|
|
61
|
+
|
|
62
|
+
- **Yastrebov, V.A.; Anciaux, G.; Molinari, J.F.** The role of the roughness spectral breadth in elastic contact of rough surfaces. *J. Mech. Phys. Solids* **107** (2017) 469–493. DOI: [10.1016/j.jmps.2017.07.016](https://doi.org/10.1016/j.jmps.2017.07.016), [arXiv:1704.05650](https://arxiv.org/abs/1704.05650)
|
|
63
|
+
|
|
64
|
+
**Python** also includes a generator with an *idealized spectrum*: Fourier magnitudes match the target exactly; only phases are random.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- Periodic Gaussian random fields in **1D/2D/3D**
|
|
71
|
+
- Self‑affine spectrum with Hurst exponent
|
|
72
|
+
- **Two methods**:
|
|
73
|
+
- *Filtered noise:* spectrum follows the target on average
|
|
74
|
+
- *Prescribed magnitudes:* exact magnitudes, random phases (real field guaranteed via Hermitian symmetry)
|
|
75
|
+
- Configurable spectral band: $k_{\text{low}}, k_{\text{high}}$
|
|
76
|
+
- Optional **PSD plateau** for $0 \le k < k_{\text{low}}$
|
|
77
|
+
- C++ implementation and pure‑NumPy Python implementation
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## C++ usage
|
|
82
|
+
|
|
83
|
+
### Build
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
make # build src/* into bin/SURFACE_GENERATOR
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Run
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
./bin/SURFACE_GENERATOR k1 k2 H N seed rms Npdf plateau
|
|
93
|
+
```
|
|
94
|
+
if you do not provide arguments, it will print the arguments it needs.
|
|
95
|
+
|
|
96
|
+
**Arguments**
|
|
97
|
+
|
|
98
|
+
+ [1] `k1 (int)` -- lower cutoff wavenumber
|
|
99
|
+
+ [2] `k2 (int)` -- upper cutoff wavenumber
|
|
100
|
+
+ [3] `H (double)` -- Hurst exponent, $0 < H < 1$
|
|
101
|
+
+ [4] `L (int)` -- number of points per size (prefer powers of 2, i.e. 128, 256, 512, etc.)
|
|
102
|
+
+ [5] `s (int)` -- seed for the random number generator
|
|
103
|
+
+ [6] `rms (double)` -- standard deviation of heights
|
|
104
|
+
+ [7] `Npdf (int)` -- number of bins in pdf data
|
|
105
|
+
+ [8] `if_plateau (bool)` -- boolean argument determining whether the power spectral density has a plateau up to k1? (0 - non, 1 -yes)
|
|
106
|
+
|
|
107
|
+
## Python usage
|
|
108
|
+
|
|
109
|
+
The Python code lives in `python/RandomField.py` with a simple test in `python/test.py`.
|
|
110
|
+
Import the module (add the folder to `PYTHONPATH` or place it next to your script):
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import numpy as np
|
|
114
|
+
import RandomField as rf
|
|
115
|
+
|
|
116
|
+
np.random.seed(42)
|
|
117
|
+
N = 1024
|
|
118
|
+
|
|
119
|
+
random_field = rf.periodic_gaussian_random_field(dim = 2, N = N, Hurst = 0.5, k_low = 4 / N, k_high = 128 / N)
|
|
120
|
+
|
|
121
|
+
ideal_random_field = rf.ideal_periodic_gaussian_random_field(dim = 2, N = N, Hurst = 0.5, k_low = 4 / N, k_high = 128 / N)
|
|
122
|
+
|
|
123
|
+
ideal_random_field_with_plateau = rf.ideal_periodic_gaussian_random_field(dim = 2, N = N, Hurst = 0.5, k_low = 4 / N, k_high = 128 / N, plateau = True)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API(Python)
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
periodic_gaussian_random_field(dim, N, Hurst, k_low, k_high, plateau=False, verbose=False)
|
|
130
|
+
-> ndarray
|
|
131
|
+
- Filtered white noise. The radially averaged PSD follows the target power law.
|
|
132
|
+
|
|
133
|
+
ideal_periodic_gaussian_random_field(dim, N, Hurst, k_low, k_high, plateau=False, verbose=False)
|
|
134
|
+
-> ndarray
|
|
135
|
+
- Exact Fourier magnitudes per bin (target spectrum), random phases only.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
|
|
140
|
+
+ `dim` (int): Dimension of the field (1, 2, or 3).
|
|
141
|
+
+ `N` (int): number of points per side in the field (N x N for 2D, N x N x N for 3D).
|
|
142
|
+
+ `Hurst` (float): Hurst exponent, in the range [0, 1].
|
|
143
|
+
+ `k_low` (float): Lower bound of the wavenumber range (k_low > 0), given in $k1/L$.
|
|
144
|
+
+ `k_high` (float): Upper bound of the wavenumber range (k_high < 0.5, which represents Nyquist frequency), given in $k2/L$.
|
|
145
|
+
+ `plateau` (bool): If True, the power spectrum is flat up to k_low, otherwise all wavenumbers below k_low are set to zero.
|
|
146
|
+
+ `verbose` (bool): If True, print the parameters used for generating the random field.
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
## Examples
|
|
150
|
+
|
|
151
|
+
Random self-affine 2D field with a "noisy" spectrum with a plateau
|
|
152
|
+
|
|
153
|
+

|
|
154
|
+
|
|
155
|
+
Random self-affine 2D field with an "ideal" spectrum with a plateau
|
|
156
|
+
|
|
157
|
+

|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
+ C++ repository: BSD 3‑Clause.
|
|
162
|
+
+ Python module: CC0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
rfgen-0.1.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Generator of a periodic random self-affine field
|
|
2
|
+
|
|
3
|
+
Generate **periodic** 1D/2D/3D self‑affine Gaussian random fields with a prescribed power‑law spectrum in Fourier space. Includes two variants: a "noisy" spectrum (filtered white noise) and an **exact‑magnitude** spectrum with random phases only. C++ and Python implementations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Project information
|
|
8
|
+
|
|
9
|
+
+ Author: Vladislav A. Yastrebov
|
|
10
|
+
+ Affiliation: CNRS, Mines Paris, Centre des Materiaux, Versailles, Paris
|
|
11
|
+
+ Date: 2015-2025
|
|
12
|
+
+ Licence: BSD 3-Clause
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Description & references
|
|
17
|
+
|
|
18
|
+
This repository implements (in C++ and Python) the Fourier‑space filtering approach introduced by:
|
|
19
|
+
|
|
20
|
+
- **Hu, Y.Z.; Tonder, K.** Simulation of 3‑D random rough surface by 2‑D digital filter in Fourier space. *Int. J. Mach. Tools Manufact.* **32** (1992) 83–90. DOI: [10.1016/0890-6955(92)90064-N](https://doi.org/10.1016/0890-6955%2892%2990064-N)
|
|
21
|
+
|
|
22
|
+
It has been used, e.g., in:
|
|
23
|
+
|
|
24
|
+
- **Yastrebov, V.A.; Anciaux, G.; Molinari, J.F.** The role of the roughness spectral breadth in elastic contact of rough surfaces. *J. Mech. Phys. Solids* **107** (2017) 469–493. DOI: [10.1016/j.jmps.2017.07.016](https://doi.org/10.1016/j.jmps.2017.07.016), [arXiv:1704.05650](https://arxiv.org/abs/1704.05650)
|
|
25
|
+
|
|
26
|
+
**Python** also includes a generator with an *idealized spectrum*: Fourier magnitudes match the target exactly; only phases are random.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Periodic Gaussian random fields in **1D/2D/3D**
|
|
33
|
+
- Self‑affine spectrum with Hurst exponent
|
|
34
|
+
- **Two methods**:
|
|
35
|
+
- *Filtered noise:* spectrum follows the target on average
|
|
36
|
+
- *Prescribed magnitudes:* exact magnitudes, random phases (real field guaranteed via Hermitian symmetry)
|
|
37
|
+
- Configurable spectral band: $k_{\text{low}}, k_{\text{high}}$
|
|
38
|
+
- Optional **PSD plateau** for $0 \le k < k_{\text{low}}$
|
|
39
|
+
- C++ implementation and pure‑NumPy Python implementation
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## C++ usage
|
|
44
|
+
|
|
45
|
+
### Build
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
make # build src/* into bin/SURFACE_GENERATOR
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Run
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
./bin/SURFACE_GENERATOR k1 k2 H N seed rms Npdf plateau
|
|
55
|
+
```
|
|
56
|
+
if you do not provide arguments, it will print the arguments it needs.
|
|
57
|
+
|
|
58
|
+
**Arguments**
|
|
59
|
+
|
|
60
|
+
+ [1] `k1 (int)` -- lower cutoff wavenumber
|
|
61
|
+
+ [2] `k2 (int)` -- upper cutoff wavenumber
|
|
62
|
+
+ [3] `H (double)` -- Hurst exponent, $0 < H < 1$
|
|
63
|
+
+ [4] `L (int)` -- number of points per size (prefer powers of 2, i.e. 128, 256, 512, etc.)
|
|
64
|
+
+ [5] `s (int)` -- seed for the random number generator
|
|
65
|
+
+ [6] `rms (double)` -- standard deviation of heights
|
|
66
|
+
+ [7] `Npdf (int)` -- number of bins in pdf data
|
|
67
|
+
+ [8] `if_plateau (bool)` -- boolean argument determining whether the power spectral density has a plateau up to k1? (0 - non, 1 -yes)
|
|
68
|
+
|
|
69
|
+
## Python usage
|
|
70
|
+
|
|
71
|
+
The Python code lives in `python/RandomField.py` with a simple test in `python/test.py`.
|
|
72
|
+
Import the module (add the folder to `PYTHONPATH` or place it next to your script):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import numpy as np
|
|
76
|
+
import RandomField as rf
|
|
77
|
+
|
|
78
|
+
np.random.seed(42)
|
|
79
|
+
N = 1024
|
|
80
|
+
|
|
81
|
+
random_field = rf.periodic_gaussian_random_field(dim = 2, N = N, Hurst = 0.5, k_low = 4 / N, k_high = 128 / N)
|
|
82
|
+
|
|
83
|
+
ideal_random_field = rf.ideal_periodic_gaussian_random_field(dim = 2, N = N, Hurst = 0.5, k_low = 4 / N, k_high = 128 / N)
|
|
84
|
+
|
|
85
|
+
ideal_random_field_with_plateau = rf.ideal_periodic_gaussian_random_field(dim = 2, N = N, Hurst = 0.5, k_low = 4 / N, k_high = 128 / N, plateau = True)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API(Python)
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
periodic_gaussian_random_field(dim, N, Hurst, k_low, k_high, plateau=False, verbose=False)
|
|
92
|
+
-> ndarray
|
|
93
|
+
- Filtered white noise. The radially averaged PSD follows the target power law.
|
|
94
|
+
|
|
95
|
+
ideal_periodic_gaussian_random_field(dim, N, Hurst, k_low, k_high, plateau=False, verbose=False)
|
|
96
|
+
-> ndarray
|
|
97
|
+
- Exact Fourier magnitudes per bin (target spectrum), random phases only.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Parameters:**
|
|
101
|
+
|
|
102
|
+
+ `dim` (int): Dimension of the field (1, 2, or 3).
|
|
103
|
+
+ `N` (int): number of points per side in the field (N x N for 2D, N x N x N for 3D).
|
|
104
|
+
+ `Hurst` (float): Hurst exponent, in the range [0, 1].
|
|
105
|
+
+ `k_low` (float): Lower bound of the wavenumber range (k_low > 0), given in $k1/L$.
|
|
106
|
+
+ `k_high` (float): Upper bound of the wavenumber range (k_high < 0.5, which represents Nyquist frequency), given in $k2/L$.
|
|
107
|
+
+ `plateau` (bool): If True, the power spectrum is flat up to k_low, otherwise all wavenumbers below k_low are set to zero.
|
|
108
|
+
+ `verbose` (bool): If True, print the parameters used for generating the random field.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## Examples
|
|
112
|
+
|
|
113
|
+
Random self-affine 2D field with a "noisy" spectrum with a plateau
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
Random self-affine 2D field with an "ideal" spectrum with a plateau
|
|
118
|
+
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
+ C++ repository: BSD 3‑Clause.
|
|
124
|
+
+ Python module: CC0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Example: Random Field Analysis
|
|
4
|
+
|
|
5
|
+
Demonstrates the analysis tools available in the rfgen package:
|
|
6
|
+
1. Autocorrelation function computation
|
|
7
|
+
2. Power spectral density (PSD) analysis
|
|
8
|
+
3. Spectral moments and derived quantities
|
|
9
|
+
|
|
10
|
+
Author: Vladislav Yastrebov, CNRS, Mines Paris - PSL
|
|
11
|
+
License: BSD-3-Clause
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
|
|
17
|
+
from rfgen import (
|
|
18
|
+
# Generation
|
|
19
|
+
selfaffine_field,
|
|
20
|
+
# Autocorrelation
|
|
21
|
+
autocorrelation_1d,
|
|
22
|
+
autocorrelation_2d,
|
|
23
|
+
correlation_length,
|
|
24
|
+
integral_correlation_length,
|
|
25
|
+
# Spectrum
|
|
26
|
+
psd_1d,
|
|
27
|
+
psd_radial_average,
|
|
28
|
+
fit_power_law,
|
|
29
|
+
estimate_hurst_exponent,
|
|
30
|
+
# Moments
|
|
31
|
+
compute_standard_moments,
|
|
32
|
+
nayak_parameter,
|
|
33
|
+
rms_quantities,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main():
|
|
38
|
+
# Parameters
|
|
39
|
+
N = 1024
|
|
40
|
+
k_low = 4 / N
|
|
41
|
+
k_high = 256 / N
|
|
42
|
+
Hurst = 0.7
|
|
43
|
+
seed = 42
|
|
44
|
+
|
|
45
|
+
rng = np.random.default_rng(seed)
|
|
46
|
+
spacing = 1.0 / N # Physical spacing
|
|
47
|
+
|
|
48
|
+
print("=" * 60)
|
|
49
|
+
print("Random Field Analysis Example")
|
|
50
|
+
print("=" * 60)
|
|
51
|
+
print(f"\nParameters:")
|
|
52
|
+
print(f" Grid size: {N}x{N}")
|
|
53
|
+
print(f" Hurst exponent (input): {Hurst}")
|
|
54
|
+
print(f" k_low = {k_low:.4f}, k_high = {k_high:.4f}")
|
|
55
|
+
|
|
56
|
+
# Generate field
|
|
57
|
+
print("\nGenerating random field...")
|
|
58
|
+
field = selfaffine_field(
|
|
59
|
+
dim=2, N=N, Hurst=Hurst, k_low=k_low, k_high=k_high, rng=rng
|
|
60
|
+
)
|
|
61
|
+
field -= np.mean(field)
|
|
62
|
+
field /= np.std(field)
|
|
63
|
+
|
|
64
|
+
# Extract a 1D profile
|
|
65
|
+
profile = field[N // 2, :]
|
|
66
|
+
|
|
67
|
+
# --- Autocorrelation Analysis ---
|
|
68
|
+
print("\n--- Autocorrelation Analysis ---")
|
|
69
|
+
|
|
70
|
+
# 1D autocorrelation
|
|
71
|
+
R_1d = autocorrelation_1d(profile)
|
|
72
|
+
l_corr = correlation_length(R_1d, threshold=0.0, spacing=spacing)
|
|
73
|
+
l_int = integral_correlation_length(R_1d, spacing=spacing)
|
|
74
|
+
|
|
75
|
+
print(f" Correlation length (1D profile): {l_corr:.4f}")
|
|
76
|
+
print(f" Integral correlation length: {l_int:.4f}")
|
|
77
|
+
|
|
78
|
+
# 2D autocorrelation
|
|
79
|
+
R_2d = autocorrelation_2d(field)
|
|
80
|
+
|
|
81
|
+
# --- Power Spectrum Analysis ---
|
|
82
|
+
print("\n--- Power Spectrum Analysis ---")
|
|
83
|
+
|
|
84
|
+
# 1D PSD
|
|
85
|
+
k_1d, psd_1d_vals = psd_1d(profile, spacing=spacing)
|
|
86
|
+
|
|
87
|
+
# Radially averaged 2D PSD
|
|
88
|
+
k_radial, psd_radial = psd_radial_average(field, spacing=spacing)
|
|
89
|
+
|
|
90
|
+
# Fit power law to estimate Hurst exponent
|
|
91
|
+
H_est, r_sq = estimate_hurst_exponent(field, k_low=k_low, k_high=k_high, spacing=spacing)
|
|
92
|
+
print(f" Estimated Hurst exponent: {H_est:.3f}")
|
|
93
|
+
print(f" R² of power-law fit: {r_sq:.4f}")
|
|
94
|
+
|
|
95
|
+
# Fit PSD directly
|
|
96
|
+
A, beta, r_sq2 = fit_power_law(k_radial, psd_radial, k_min=k_low, k_max=k_high)
|
|
97
|
+
print(f" PSD power-law exponent β: {beta:.3f}")
|
|
98
|
+
print(f" (Expected: β = dim + 2H = {2 + 2*Hurst:.3f})")
|
|
99
|
+
|
|
100
|
+
# --- Spectral Moments ---
|
|
101
|
+
print("\n--- Spectral Moments ---")
|
|
102
|
+
|
|
103
|
+
moments = compute_standard_moments(field, spacing=spacing)
|
|
104
|
+
print(" Standard moments:")
|
|
105
|
+
for name, value in moments.items():
|
|
106
|
+
print(f" {name}: {value:.6e}")
|
|
107
|
+
|
|
108
|
+
alpha = nayak_parameter(field, spacing=spacing)
|
|
109
|
+
print(f"\n Nayak's bandwidth parameter α: {alpha:.3f}")
|
|
110
|
+
|
|
111
|
+
rms = rms_quantities(field, spacing=spacing)
|
|
112
|
+
print("\n RMS quantities:")
|
|
113
|
+
for name, value in rms.items():
|
|
114
|
+
print(f" {name}: {value:.6e}")
|
|
115
|
+
|
|
116
|
+
# --- Plotting ---
|
|
117
|
+
fig = plt.figure(figsize=(14, 10))
|
|
118
|
+
|
|
119
|
+
# 1. Field
|
|
120
|
+
ax1 = fig.add_subplot(2, 3, 1)
|
|
121
|
+
im = ax1.imshow(field, cmap="RdYlBu_r", interpolation="bicubic")
|
|
122
|
+
ax1.set_title(f"Random Field (H = {Hurst})")
|
|
123
|
+
ax1.set_xlabel("x")
|
|
124
|
+
ax1.set_ylabel("y")
|
|
125
|
+
plt.colorbar(im, ax=ax1, shrink=0.8)
|
|
126
|
+
|
|
127
|
+
# 2. 1D Profile
|
|
128
|
+
ax2 = fig.add_subplot(2, 3, 2)
|
|
129
|
+
x = np.linspace(0, 1, N)
|
|
130
|
+
ax2.plot(x, profile, "b-", lw=0.5)
|
|
131
|
+
ax2.set_xlabel("x / L")
|
|
132
|
+
ax2.set_ylabel("z / σ")
|
|
133
|
+
ax2.set_title("1D Profile")
|
|
134
|
+
ax2.set_xlim(0, 1)
|
|
135
|
+
ax2.grid(True, alpha=0.3)
|
|
136
|
+
|
|
137
|
+
# 3. 1D Autocorrelation
|
|
138
|
+
ax3 = fig.add_subplot(2, 3, 3)
|
|
139
|
+
dx = np.linspace(0, 0.5, N // 2)
|
|
140
|
+
ax3.plot(dx, R_1d[: N // 2], "b-", lw=1.5)
|
|
141
|
+
ax3.axhline(0, color="k", ls="--", lw=0.5)
|
|
142
|
+
ax3.axvline(l_corr, color="r", ls="--", lw=1, label=f"l* = {l_corr:.3f}")
|
|
143
|
+
ax3.set_xlabel("Δx / L")
|
|
144
|
+
ax3.set_ylabel("R(Δx)")
|
|
145
|
+
ax3.set_title("Autocorrelation Function (1D)")
|
|
146
|
+
ax3.set_xlim(0, 0.5)
|
|
147
|
+
ax3.legend()
|
|
148
|
+
ax3.grid(True, alpha=0.3)
|
|
149
|
+
|
|
150
|
+
# 4. 2D Autocorrelation
|
|
151
|
+
ax4 = fig.add_subplot(2, 3, 4)
|
|
152
|
+
extent = [0, 0.3, 0, 0.3]
|
|
153
|
+
n_show = int(0.3 * N)
|
|
154
|
+
im4 = ax4.imshow(
|
|
155
|
+
R_2d[:n_show, :n_show],
|
|
156
|
+
cmap="RdYlBu_r",
|
|
157
|
+
interpolation="bicubic",
|
|
158
|
+
extent=extent,
|
|
159
|
+
origin="lower",
|
|
160
|
+
)
|
|
161
|
+
ax4.set_xlabel("Δx / L")
|
|
162
|
+
ax4.set_ylabel("Δy / L")
|
|
163
|
+
ax4.set_title("2D Autocorrelation")
|
|
164
|
+
plt.colorbar(im4, ax=ax4, shrink=0.8)
|
|
165
|
+
|
|
166
|
+
# 5. Radial PSD (log-log)
|
|
167
|
+
ax5 = fig.add_subplot(2, 3, 5)
|
|
168
|
+
valid = psd_radial > 0
|
|
169
|
+
ax5.loglog(k_radial[valid], psd_radial[valid], "b.", ms=3, alpha=0.5, label="Data")
|
|
170
|
+
# Plot fit
|
|
171
|
+
k_fit = np.logspace(np.log10(k_low), np.log10(k_high), 100)
|
|
172
|
+
psd_fit = A * k_fit ** (-beta)
|
|
173
|
+
ax5.loglog(k_fit, psd_fit, "r-", lw=2, label=f"Fit: k^{{-{beta:.2f}}}")
|
|
174
|
+
ax5.axvline(k_low, color="g", ls=":", label=f"k_low = {k_low:.3f}")
|
|
175
|
+
ax5.axvline(k_high, color="g", ls=":")
|
|
176
|
+
ax5.set_xlabel("k")
|
|
177
|
+
ax5.set_ylabel("PSD(k)")
|
|
178
|
+
ax5.set_title("Radially Averaged PSD")
|
|
179
|
+
ax5.legend(fontsize=8)
|
|
180
|
+
ax5.grid(True, alpha=0.3, which="both")
|
|
181
|
+
|
|
182
|
+
# 6. Summary statistics
|
|
183
|
+
ax6 = fig.add_subplot(2, 3, 6)
|
|
184
|
+
ax6.axis("off")
|
|
185
|
+
summary = [
|
|
186
|
+
f"Input Hurst: {Hurst:.2f}",
|
|
187
|
+
f"Estimated Hurst: {H_est:.3f}",
|
|
188
|
+
f"PSD exponent β: {beta:.3f}",
|
|
189
|
+
f"Correlation length: {l_corr:.4f}",
|
|
190
|
+
f"Bandwidth α: {alpha:.3f}",
|
|
191
|
+
"",
|
|
192
|
+
f"RMS height: {rms['rms_height']:.4f}",
|
|
193
|
+
f"RMS slope: {rms['rms_slope']:.4f}",
|
|
194
|
+
f"RMS curvature: {rms['rms_curvature']:.4f}",
|
|
195
|
+
]
|
|
196
|
+
ax6.text(
|
|
197
|
+
0.1,
|
|
198
|
+
0.9,
|
|
199
|
+
"\n".join(summary),
|
|
200
|
+
transform=ax6.transAxes,
|
|
201
|
+
fontsize=11,
|
|
202
|
+
verticalalignment="top",
|
|
203
|
+
fontfamily="monospace",
|
|
204
|
+
bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5),
|
|
205
|
+
)
|
|
206
|
+
ax6.set_title("Summary Statistics")
|
|
207
|
+
|
|
208
|
+
plt.tight_layout()
|
|
209
|
+
plt.savefig("random_field_analysis.png", dpi=150)
|
|
210
|
+
plt.show()
|
|
211
|
+
|
|
212
|
+
print("\n" + "=" * 60)
|
|
213
|
+
print("Analysis complete! Figure saved as 'random_field_analysis.png'")
|
|
214
|
+
print("=" * 60)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Example: Random Field Generation
|
|
4
|
+
|
|
5
|
+
Demonstrates how to generate periodic Gaussian random fields with different
|
|
6
|
+
spectral characteristics:
|
|
7
|
+
1. Self-affine spectrum (power-law)
|
|
8
|
+
2. Matérn covariance spectrum
|
|
9
|
+
3. Filtered white noise vs. ideal spectrum methods (noise parameter)
|
|
10
|
+
|
|
11
|
+
Author: Vladislav Yastrebov, CNRS, Mines Paris - PSL
|
|
12
|
+
License: BSD-3-Clause
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
|
|
18
|
+
from rfgen import selfaffine_field, matern_field
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def plot_field(field, title, ax=None, cmap="RdYlBu_r"):
|
|
22
|
+
"""Helper function to plot a 2D field."""
|
|
23
|
+
if ax is None:
|
|
24
|
+
fig, ax = plt.subplots()
|
|
25
|
+
im = ax.imshow(field, cmap=cmap, interpolation="bicubic")
|
|
26
|
+
ax.set_title(title)
|
|
27
|
+
ax.set_xticks([])
|
|
28
|
+
ax.set_yticks([])
|
|
29
|
+
ax.set_xlabel("x")
|
|
30
|
+
ax.set_ylabel("y")
|
|
31
|
+
return im
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
# Parameters
|
|
36
|
+
N = 512 # Grid size
|
|
37
|
+
k_low = 8 / N # Lower cutoff wavenumber
|
|
38
|
+
k_high = 128 / N # Upper cutoff wavenumber
|
|
39
|
+
Hurst = 0.7 # Hurst exponent for self-affine
|
|
40
|
+
seed = 42 # Random seed for reproducibility
|
|
41
|
+
|
|
42
|
+
# Create random number generator
|
|
43
|
+
rng = np.random.default_rng(seed)
|
|
44
|
+
|
|
45
|
+
print("Generating random fields...")
|
|
46
|
+
print(f" Grid size: {N}x{N}")
|
|
47
|
+
print(f" k_low = {k_low:.4f}, k_high = {k_high:.4f}")
|
|
48
|
+
print(f" Hurst exponent: {Hurst}")
|
|
49
|
+
|
|
50
|
+
# --- Self-affine fields ---
|
|
51
|
+
# Method 1: Filtered white noise (noise=True, default)
|
|
52
|
+
field_noise = selfaffine_field(
|
|
53
|
+
dim=2, N=N, Hurst=Hurst, k_low=k_low, k_high=k_high, noise=True, rng=rng
|
|
54
|
+
)
|
|
55
|
+
field_noise /= np.std(field_noise) # Normalize
|
|
56
|
+
|
|
57
|
+
# Method 2: Ideal spectrum with random phase (noise=False)
|
|
58
|
+
rng2 = np.random.default_rng(seed) # Reset RNG
|
|
59
|
+
field_ideal = selfaffine_field(
|
|
60
|
+
dim=2, N=N, Hurst=Hurst, k_low=k_low, k_high=k_high, noise=False, rng=rng2
|
|
61
|
+
)
|
|
62
|
+
field_ideal /= np.std(field_ideal)
|
|
63
|
+
|
|
64
|
+
# With plateau (roll-off at low frequencies)
|
|
65
|
+
rng3 = np.random.default_rng(seed)
|
|
66
|
+
field_plateau = selfaffine_field(
|
|
67
|
+
dim=2, N=N, Hurst=Hurst, k_low=k_low, k_high=k_high, plateau=True, rng=rng3
|
|
68
|
+
)
|
|
69
|
+
field_plateau /= np.std(field_plateau)
|
|
70
|
+
|
|
71
|
+
# --- Matérn fields ---
|
|
72
|
+
rng4 = np.random.default_rng(seed)
|
|
73
|
+
field_matern_noise = matern_field(
|
|
74
|
+
dim=2,
|
|
75
|
+
N=N,
|
|
76
|
+
nu=1.5, # Smoothness parameter
|
|
77
|
+
correlation_length=0.05,
|
|
78
|
+
sigma=1.0,
|
|
79
|
+
k_low=k_low,
|
|
80
|
+
k_high=k_high,
|
|
81
|
+
noise=True, # With spectral noise
|
|
82
|
+
rng=rng4,
|
|
83
|
+
)
|
|
84
|
+
field_matern_noise /= np.std(field_matern_noise)
|
|
85
|
+
|
|
86
|
+
rng5 = np.random.default_rng(seed)
|
|
87
|
+
field_matern_ideal = matern_field(
|
|
88
|
+
dim=2,
|
|
89
|
+
N=N,
|
|
90
|
+
nu=1.5,
|
|
91
|
+
correlation_length=0.05,
|
|
92
|
+
sigma=1.0,
|
|
93
|
+
k_low=k_low,
|
|
94
|
+
k_high=k_high,
|
|
95
|
+
noise=False, # Ideal spectrum
|
|
96
|
+
rng=rng5,
|
|
97
|
+
)
|
|
98
|
+
field_matern_ideal /= np.std(field_matern_ideal)
|
|
99
|
+
|
|
100
|
+
# --- Plotting ---
|
|
101
|
+
fig, axes = plt.subplots(2, 3, figsize=(14, 9))
|
|
102
|
+
|
|
103
|
+
# Self-affine fields
|
|
104
|
+
im1 = plot_field(field_noise, f"Self-affine (noise=True)\nH = {Hurst}", axes[0, 0])
|
|
105
|
+
im2 = plot_field(field_ideal, f"Self-affine (noise=False)\nH = {Hurst}", axes[0, 1])
|
|
106
|
+
im3 = plot_field(field_plateau, f"Self-affine (with plateau)\nH = {Hurst}", axes[0, 2])
|
|
107
|
+
|
|
108
|
+
# Matérn fields
|
|
109
|
+
im4 = plot_field(field_matern_noise, "Matérn (noise=True)\nν = 1.5", axes[1, 0])
|
|
110
|
+
im5 = plot_field(field_matern_ideal, "Matérn (noise=False)\nν = 1.5", axes[1, 1])
|
|
111
|
+
|
|
112
|
+
# Hide empty subplot
|
|
113
|
+
axes[1, 2].axis("off")
|
|
114
|
+
|
|
115
|
+
# Add colorbars
|
|
116
|
+
for ax, im in zip(axes.flat[:5], [im1, im2, im3, im4, im5]):
|
|
117
|
+
fig.colorbar(im, ax=ax, orientation="horizontal", pad=0.05, shrink=0.8)
|
|
118
|
+
|
|
119
|
+
plt.suptitle("Periodic Gaussian Random Fields", fontsize=14, fontweight="bold")
|
|
120
|
+
plt.tight_layout()
|
|
121
|
+
plt.savefig("random_fields_comparison.png", dpi=150)
|
|
122
|
+
plt.show()
|
|
123
|
+
|
|
124
|
+
print("\nFields generated successfully!")
|
|
125
|
+
print("Figure saved as 'random_fields_comparison.png'")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
main()
|