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 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
+
@@ -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
+ ![Random self-affine 2D field with a "noisy" spectrum](python/RandomField_2D_filtering.png)
154
+
155
+ Random self-affine 2D field with an "ideal" spectrum with a plateau
156
+
157
+ ![Random self-affine 2D field with an "ideal" spectrum](python/RandomField_2D_ideal.png)
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
+ ![Random self-affine 2D field with a "noisy" spectrum](python/RandomField_2D_filtering.png)
116
+
117
+ Random self-affine 2D field with an "ideal" spectrum with a plateau
118
+
119
+ ![Random self-affine 2D field with an "ideal" spectrum](python/RandomField_2D_ideal.png)
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()