random-fields 1.0.1__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.
- random_fields-1.0.1/LICENSE +28 -0
- random_fields-1.0.1/PKG-INFO +153 -0
- random_fields-1.0.1/README.md +130 -0
- random_fields-1.0.1/pyproject.toml +3 -0
- random_fields-1.0.1/random_fields/__init__.py +1 -0
- random_fields-1.0.1/random_fields/__version__.py +3 -0
- random_fields-1.0.1/random_fields/generate_field.py +96 -0
- random_fields-1.0.1/random_fields/utils.py +97 -0
- random_fields-1.0.1/random_fields.egg-info/PKG-INFO +153 -0
- random_fields-1.0.1/random_fields.egg-info/SOURCES.txt +16 -0
- random_fields-1.0.1/random_fields.egg-info/dependency_links.txt +1 -0
- random_fields-1.0.1/random_fields.egg-info/requires.txt +12 -0
- random_fields-1.0.1/random_fields.egg-info/top_level.txt +1 -0
- random_fields-1.0.1/setup.cfg +35 -0
- random_fields-1.0.1/setup.py +4 -0
- random_fields-1.0.1/tests/test_random_field.py +99 -0
- random_fields-1.0.1/tests/test_random_field_utils.py +70 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2025, StemVibrations
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: random_fields
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Random fields on unstructured grids
|
|
5
|
+
Author: attr: random_fields.__author__
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: Unix
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=2.0.2
|
|
14
|
+
Requires-Dist: matplotlib>=3.9.3
|
|
15
|
+
Requires-Dist: gstools>=1.6.0
|
|
16
|
+
Provides-Extra: testing
|
|
17
|
+
Requires-Dist: pytest>=7.4.2; extra == "testing"
|
|
18
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "testing"
|
|
19
|
+
Requires-Dist: tox>=4.4; extra == "testing"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: Sphinx==6.2.1; extra == "docs"
|
|
22
|
+
Requires-Dist: sphinx-rtd-theme==1.2.2; extra == "docs"
|
|
23
|
+
|
|
24
|
+
# RandomFields
|
|
25
|
+

|
|
26
|
+
[](https://codecov.io/gh/StemVibrations/RandomFields)
|
|
27
|
+
|
|
28
|
+
# Random Fields
|
|
29
|
+
|
|
30
|
+
Python package to generate random fields in 2D or 3D on unstructured grids.
|
|
31
|
+
|
|
32
|
+
## Background information
|
|
33
|
+
The random fields are generated using [gstools](https://geostat-framework.readthedocs.io/en/latest/).
|
|
34
|
+
|
|
35
|
+
Currently, the following covariance models are available:
|
|
36
|
+
- Gaussian
|
|
37
|
+
- Exponential
|
|
38
|
+
- Matern
|
|
39
|
+
- Linear
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
To install the package, run the following command in the terminal:
|
|
44
|
+
```bash
|
|
45
|
+
pip install git+https://github.com/StemVibrations/RandomFields
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
# Tutorial
|
|
49
|
+
## Example in 2D
|
|
50
|
+
|
|
51
|
+
In this example we generate a random field on a 2D regular grid.
|
|
52
|
+
|
|
53
|
+
First you need to import the packages:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import numpy as np
|
|
57
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
58
|
+
from random_fields.utils import plot2D
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then you need to define the grid:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
x = np.linspace(0, 100, 50)
|
|
65
|
+
y = np.linspace(0, 50, 50)
|
|
66
|
+
|
|
67
|
+
x, y = np.meshgrid(x, y)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
In this example we create a mesh of 50x50 points between 0 and 100 in x and 0 and 50 in y direction.
|
|
71
|
+
|
|
72
|
+
Then we define the random field properties:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
nb_dimensions = 2
|
|
76
|
+
mean = 10
|
|
77
|
+
variance = 2
|
|
78
|
+
vertical_scale_fluctuation = 10
|
|
79
|
+
anisotropy = [1]
|
|
80
|
+
angle = [0]
|
|
81
|
+
model_rf = ModelName.Gaussian
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
In this example we use a Gaussian covariance model.
|
|
85
|
+
|
|
86
|
+
Then we create the random field:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
rf = RandomFields(model_rf, nb_dimensions, mean, variance, vertical_scale_fluctuation, anisotropy, angle, seed=14)
|
|
90
|
+
rf.generate(np.array([x.ravel(), y.ravel()]).T)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To visualise the results you can run:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
plot2D([np.array([x.ravel(), y.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field.png")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The result is the following random field:
|
|
100
|
+
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+
## Example in 3D
|
|
104
|
+
|
|
105
|
+
In this example we generate a random field on a 3D regular grid.
|
|
106
|
+
|
|
107
|
+
First you need to import the packages:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import numpy as np
|
|
111
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
112
|
+
from random_fields.utils import plot3D
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then you need to define the grid:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
x = np.linspace(0, 100, 50)
|
|
119
|
+
y = np.linspace(0, 50, 50)
|
|
120
|
+
z = np.linspace(0, 25, 25)
|
|
121
|
+
|
|
122
|
+
x, y, z = np.meshgrid(x, y, z)
|
|
123
|
+
```
|
|
124
|
+
In this example we create a mesh of 50x50x25 points between 0 and 100 in x, 0 and 50 in y direction and 0 and 25 in z direction.
|
|
125
|
+
|
|
126
|
+
Then we define the random field properties:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
nb_dimensions = 3
|
|
130
|
+
mean = 10
|
|
131
|
+
variance = 2
|
|
132
|
+
vertical_scale_fluctuation = 10
|
|
133
|
+
anisotropy = [5, 5]
|
|
134
|
+
angle = [0, 0]
|
|
135
|
+
model_rf = ModelName.Gaussian
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then we create the random field:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
rf = RandomFields(model_rf, nb_dimensions, mean, variance, vertical_scale_fluctuation, anisotropy, angle, seed=14)
|
|
142
|
+
rf.generate(np.array([x.ravel(), y.ravel(), z.ravel()]).T)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
To visualise the results you can run:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
plot3D([np.array([x.ravel(), y.ravel(), z.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field_3D.png")
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
The result is the following random field:
|
|
152
|
+
|
|
153
|
+

|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# RandomFields
|
|
2
|
+

|
|
3
|
+
[](https://codecov.io/gh/StemVibrations/RandomFields)
|
|
4
|
+
|
|
5
|
+
# Random Fields
|
|
6
|
+
|
|
7
|
+
Python package to generate random fields in 2D or 3D on unstructured grids.
|
|
8
|
+
|
|
9
|
+
## Background information
|
|
10
|
+
The random fields are generated using [gstools](https://geostat-framework.readthedocs.io/en/latest/).
|
|
11
|
+
|
|
12
|
+
Currently, the following covariance models are available:
|
|
13
|
+
- Gaussian
|
|
14
|
+
- Exponential
|
|
15
|
+
- Matern
|
|
16
|
+
- Linear
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
To install the package, run the following command in the terminal:
|
|
21
|
+
```bash
|
|
22
|
+
pip install git+https://github.com/StemVibrations/RandomFields
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
# Tutorial
|
|
26
|
+
## Example in 2D
|
|
27
|
+
|
|
28
|
+
In this example we generate a random field on a 2D regular grid.
|
|
29
|
+
|
|
30
|
+
First you need to import the packages:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import numpy as np
|
|
34
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
35
|
+
from random_fields.utils import plot2D
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then you need to define the grid:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
x = np.linspace(0, 100, 50)
|
|
42
|
+
y = np.linspace(0, 50, 50)
|
|
43
|
+
|
|
44
|
+
x, y = np.meshgrid(x, y)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
In this example we create a mesh of 50x50 points between 0 and 100 in x and 0 and 50 in y direction.
|
|
48
|
+
|
|
49
|
+
Then we define the random field properties:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
nb_dimensions = 2
|
|
53
|
+
mean = 10
|
|
54
|
+
variance = 2
|
|
55
|
+
vertical_scale_fluctuation = 10
|
|
56
|
+
anisotropy = [1]
|
|
57
|
+
angle = [0]
|
|
58
|
+
model_rf = ModelName.Gaussian
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
In this example we use a Gaussian covariance model.
|
|
62
|
+
|
|
63
|
+
Then we create the random field:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
rf = RandomFields(model_rf, nb_dimensions, mean, variance, vertical_scale_fluctuation, anisotropy, angle, seed=14)
|
|
67
|
+
rf.generate(np.array([x.ravel(), y.ravel()]).T)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To visualise the results you can run:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
plot2D([np.array([x.ravel(), y.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field.png")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The result is the following random field:
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
## Example in 3D
|
|
81
|
+
|
|
82
|
+
In this example we generate a random field on a 3D regular grid.
|
|
83
|
+
|
|
84
|
+
First you need to import the packages:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import numpy as np
|
|
88
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
89
|
+
from random_fields.utils import plot3D
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then you need to define the grid:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
x = np.linspace(0, 100, 50)
|
|
96
|
+
y = np.linspace(0, 50, 50)
|
|
97
|
+
z = np.linspace(0, 25, 25)
|
|
98
|
+
|
|
99
|
+
x, y, z = np.meshgrid(x, y, z)
|
|
100
|
+
```
|
|
101
|
+
In this example we create a mesh of 50x50x25 points between 0 and 100 in x, 0 and 50 in y direction and 0 and 25 in z direction.
|
|
102
|
+
|
|
103
|
+
Then we define the random field properties:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
nb_dimensions = 3
|
|
107
|
+
mean = 10
|
|
108
|
+
variance = 2
|
|
109
|
+
vertical_scale_fluctuation = 10
|
|
110
|
+
anisotropy = [5, 5]
|
|
111
|
+
angle = [0, 0]
|
|
112
|
+
model_rf = ModelName.Gaussian
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then we create the random field:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
rf = RandomFields(model_rf, nb_dimensions, mean, variance, vertical_scale_fluctuation, anisotropy, angle, seed=14)
|
|
119
|
+
rf.generate(np.array([x.ravel(), y.ravel(), z.ravel()]).T)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
To visualise the results you can run:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
plot3D([np.array([x.ravel(), y.ravel(), z.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field_3D.png")
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
The result is the following random field:
|
|
129
|
+
|
|
130
|
+

|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .__version__ import __version__
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List
|
|
3
|
+
import numpy as np
|
|
4
|
+
import gstools as gs
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ModelName(Enum):
|
|
8
|
+
"""
|
|
9
|
+
Name of the model to be used. Options are: "Gaussian", "Exponential", "Matern", "Linear"
|
|
10
|
+
"""
|
|
11
|
+
Gaussian = gs.Gaussian
|
|
12
|
+
Exponential = gs.Exponential
|
|
13
|
+
Matern = gs.Matern
|
|
14
|
+
Linear = gs.Linear
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RandomFields:
|
|
18
|
+
"""
|
|
19
|
+
Generate random fields
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, model_name: ModelName, n_dim: int,
|
|
23
|
+
mean: float, variance: float,
|
|
24
|
+
v_scale_fluctuation: float, anisotropy: List[float], angle: List[float],
|
|
25
|
+
seed: int = 14, v_dim: int = 1) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Initialise generation of random fields
|
|
28
|
+
|
|
29
|
+
Parameters:
|
|
30
|
+
-----------
|
|
31
|
+
model_name: str
|
|
32
|
+
Name of the model to be used. Options are: "Gaussian", "Exponential", "Matern", "Linear"
|
|
33
|
+
n_dim: int
|
|
34
|
+
The dimension of the random field
|
|
35
|
+
mean: float
|
|
36
|
+
The mean of the random field
|
|
37
|
+
variance: float
|
|
38
|
+
The variance of the random field
|
|
39
|
+
v_scale_fluctuation: float
|
|
40
|
+
The vertical scale of fluctuation of the random field
|
|
41
|
+
anisotropy: list
|
|
42
|
+
The anisotropy of the random field (per dimension)
|
|
43
|
+
angle: list
|
|
44
|
+
The angle of the random field (per dimension)
|
|
45
|
+
seed: int
|
|
46
|
+
The seed number for the random number generator
|
|
47
|
+
v_dim: int
|
|
48
|
+
The dimension of the vertical scale of fluctuation
|
|
49
|
+
"""
|
|
50
|
+
# initialise model
|
|
51
|
+
if model_name.name not in ModelName.__members__:
|
|
52
|
+
raise ValueError(f'Model name: {model_name} is not supported')
|
|
53
|
+
|
|
54
|
+
if n_dim not in [1, 2, 3]:
|
|
55
|
+
raise ValueError('Only 1, 2, 3 dimensions are supported')
|
|
56
|
+
|
|
57
|
+
aux_keys = [anisotropy, angle]
|
|
58
|
+
for key in aux_keys:
|
|
59
|
+
if len(key) != n_dim - 1:
|
|
60
|
+
raise ValueError(f'Number of dimensions: {n_dim} does not match number of\
|
|
61
|
+
random field properties: {len(variance)}. It should be {n_dim - 1}.')
|
|
62
|
+
|
|
63
|
+
self.random_field_model = model_name.value
|
|
64
|
+
self.n_dim = n_dim
|
|
65
|
+
self.seed = seed
|
|
66
|
+
self.mean = mean
|
|
67
|
+
self.variance = variance
|
|
68
|
+
self.vertical_scale_fluctuation = v_scale_fluctuation
|
|
69
|
+
self.anisotropy = anisotropy
|
|
70
|
+
self.angle = angle
|
|
71
|
+
self.v_dim = v_dim
|
|
72
|
+
self.random_field = None
|
|
73
|
+
|
|
74
|
+
def generate(self, nodes: np.ndarray) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Generate random field
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
------------
|
|
80
|
+
nodes: list
|
|
81
|
+
The nodes of the random field
|
|
82
|
+
"""
|
|
83
|
+
# check dimensions of nodes agrees with dimensions of model
|
|
84
|
+
if nodes.shape[1] != self.n_dim:
|
|
85
|
+
raise ValueError(f'Dimensions of nodes: {nodes.shape[1]} do not match dimensions of model: {self.n_dim}')
|
|
86
|
+
|
|
87
|
+
# scale of fluctuation
|
|
88
|
+
scale_fluctuation = np.ones(self.n_dim) * self.vertical_scale_fluctuation
|
|
89
|
+
|
|
90
|
+
# apply the anisotropy to the other dimensions
|
|
91
|
+
mask = np.arange(len(scale_fluctuation)) != self.v_dim
|
|
92
|
+
scale_fluctuation[mask] = scale_fluctuation[mask] * self.anisotropy
|
|
93
|
+
|
|
94
|
+
model = self.random_field_model(dim=self.n_dim, var=self.variance, len_scale=scale_fluctuation, angles=self.angle)
|
|
95
|
+
self.random_field = gs.SRF(model, mean=self.mean, seed=self.seed)
|
|
96
|
+
self.random_field(nodes.T)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pylab as plt
|
|
4
|
+
import matplotlib as mpl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def plot3D(coordinates: list, random_field: list, title: str = "Random Field",
|
|
8
|
+
output_folder = "./", output_name: str = "random_field.png"):
|
|
9
|
+
"""
|
|
10
|
+
Plot 3D random field
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
coordinates : list
|
|
15
|
+
List of coordinates of the random field
|
|
16
|
+
random_field : list
|
|
17
|
+
List of random field values
|
|
18
|
+
title : str
|
|
19
|
+
Title of the plot
|
|
20
|
+
output_folder : str
|
|
21
|
+
Output folder
|
|
22
|
+
output_name : str
|
|
23
|
+
Output fine name
|
|
24
|
+
"""
|
|
25
|
+
# create output folder
|
|
26
|
+
if not os.path.exists(output_folder):
|
|
27
|
+
os.makedirs(output_folder)
|
|
28
|
+
|
|
29
|
+
# make plot
|
|
30
|
+
fig = plt.figure(1, figsize=(6, 5))
|
|
31
|
+
ax = fig.add_subplot(projection='3d')
|
|
32
|
+
ax.set_position([0.1, 0.1, 0.8, 0.8])
|
|
33
|
+
# rotate the axes so that y is vertical
|
|
34
|
+
ax.view_init(125, -90, 0)
|
|
35
|
+
|
|
36
|
+
vmin = np.min([np.min(aux) for aux in random_field])
|
|
37
|
+
vmax = np.max([np.max(aux) for aux in random_field])
|
|
38
|
+
|
|
39
|
+
for i, coord in enumerate(coordinates):
|
|
40
|
+
x, y, z = coord[:, 0], coord[:, 1], coord[:, 2]
|
|
41
|
+
ax.scatter(x, y, z, c=random_field[i], vmin=vmin, vmax=vmax, cmap="viridis", edgecolors=None, marker="s")
|
|
42
|
+
|
|
43
|
+
ax.set_xlabel('x coordinate')
|
|
44
|
+
ax.set_ylabel('y coordinate')
|
|
45
|
+
ax.set_zlabel('z coordinate')
|
|
46
|
+
|
|
47
|
+
cax = ax.inset_axes([1.1, 0., 0.05, 1])
|
|
48
|
+
norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
|
|
49
|
+
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap="viridis"), ax=ax, cax=cax)
|
|
50
|
+
fig.suptitle(title)
|
|
51
|
+
plt.savefig(os.path.join(output_folder, output_name))
|
|
52
|
+
plt.close()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def plot2D(coordinates: list, random_field: list, title: str = "Random Field",
|
|
56
|
+
output_folder = "./", output_name: str = "random_field.png"):
|
|
57
|
+
"""
|
|
58
|
+
Plot 2D random field
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
coordinates : list
|
|
63
|
+
List of coordinates of the random field
|
|
64
|
+
random_field : list
|
|
65
|
+
List of random field values
|
|
66
|
+
title : str
|
|
67
|
+
Title of the plot
|
|
68
|
+
output_folder : str
|
|
69
|
+
Output folder
|
|
70
|
+
output_name : str
|
|
71
|
+
Output fine name
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# create output folder
|
|
75
|
+
if not os.path.exists(output_folder):
|
|
76
|
+
os.makedirs(output_folder)
|
|
77
|
+
|
|
78
|
+
# make plot
|
|
79
|
+
fig, ax = plt.subplots(figsize=(6, 5))
|
|
80
|
+
ax.set_position([0.1, 0.1, 0.7, 0.8])
|
|
81
|
+
|
|
82
|
+
vmin = np.min([np.min(aux) for aux in random_field])
|
|
83
|
+
vmax = np.max([np.max(aux) for aux in random_field])
|
|
84
|
+
|
|
85
|
+
for i, coord in enumerate(coordinates):
|
|
86
|
+
x, y = coord[:, 0], coord[:, 1]
|
|
87
|
+
ax.scatter(x, y, c=random_field[i], vmin=vmin, vmax=vmax, cmap="viridis", edgecolors=None, marker="s")
|
|
88
|
+
|
|
89
|
+
ax.set_xlabel('x coordinate')
|
|
90
|
+
ax.set_ylabel('y coordinate')
|
|
91
|
+
|
|
92
|
+
cax = ax.inset_axes([1.1, 0., 0.05, 1])
|
|
93
|
+
norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
|
|
94
|
+
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap="viridis"), ax=ax, cax=cax)
|
|
95
|
+
fig.suptitle(title)
|
|
96
|
+
plt.savefig(os.path.join(output_folder, output_name))
|
|
97
|
+
plt.close()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: random_fields
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Random fields on unstructured grids
|
|
5
|
+
Author: attr: random_fields.__author__
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: Unix
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=2.0.2
|
|
14
|
+
Requires-Dist: matplotlib>=3.9.3
|
|
15
|
+
Requires-Dist: gstools>=1.6.0
|
|
16
|
+
Provides-Extra: testing
|
|
17
|
+
Requires-Dist: pytest>=7.4.2; extra == "testing"
|
|
18
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "testing"
|
|
19
|
+
Requires-Dist: tox>=4.4; extra == "testing"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: Sphinx==6.2.1; extra == "docs"
|
|
22
|
+
Requires-Dist: sphinx-rtd-theme==1.2.2; extra == "docs"
|
|
23
|
+
|
|
24
|
+
# RandomFields
|
|
25
|
+

|
|
26
|
+
[](https://codecov.io/gh/StemVibrations/RandomFields)
|
|
27
|
+
|
|
28
|
+
# Random Fields
|
|
29
|
+
|
|
30
|
+
Python package to generate random fields in 2D or 3D on unstructured grids.
|
|
31
|
+
|
|
32
|
+
## Background information
|
|
33
|
+
The random fields are generated using [gstools](https://geostat-framework.readthedocs.io/en/latest/).
|
|
34
|
+
|
|
35
|
+
Currently, the following covariance models are available:
|
|
36
|
+
- Gaussian
|
|
37
|
+
- Exponential
|
|
38
|
+
- Matern
|
|
39
|
+
- Linear
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
To install the package, run the following command in the terminal:
|
|
44
|
+
```bash
|
|
45
|
+
pip install git+https://github.com/StemVibrations/RandomFields
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
# Tutorial
|
|
49
|
+
## Example in 2D
|
|
50
|
+
|
|
51
|
+
In this example we generate a random field on a 2D regular grid.
|
|
52
|
+
|
|
53
|
+
First you need to import the packages:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import numpy as np
|
|
57
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
58
|
+
from random_fields.utils import plot2D
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then you need to define the grid:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
x = np.linspace(0, 100, 50)
|
|
65
|
+
y = np.linspace(0, 50, 50)
|
|
66
|
+
|
|
67
|
+
x, y = np.meshgrid(x, y)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
In this example we create a mesh of 50x50 points between 0 and 100 in x and 0 and 50 in y direction.
|
|
71
|
+
|
|
72
|
+
Then we define the random field properties:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
nb_dimensions = 2
|
|
76
|
+
mean = 10
|
|
77
|
+
variance = 2
|
|
78
|
+
vertical_scale_fluctuation = 10
|
|
79
|
+
anisotropy = [1]
|
|
80
|
+
angle = [0]
|
|
81
|
+
model_rf = ModelName.Gaussian
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
In this example we use a Gaussian covariance model.
|
|
85
|
+
|
|
86
|
+
Then we create the random field:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
rf = RandomFields(model_rf, nb_dimensions, mean, variance, vertical_scale_fluctuation, anisotropy, angle, seed=14)
|
|
90
|
+
rf.generate(np.array([x.ravel(), y.ravel()]).T)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To visualise the results you can run:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
plot2D([np.array([x.ravel(), y.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field.png")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The result is the following random field:
|
|
100
|
+
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+
## Example in 3D
|
|
104
|
+
|
|
105
|
+
In this example we generate a random field on a 3D regular grid.
|
|
106
|
+
|
|
107
|
+
First you need to import the packages:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import numpy as np
|
|
111
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
112
|
+
from random_fields.utils import plot3D
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then you need to define the grid:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
x = np.linspace(0, 100, 50)
|
|
119
|
+
y = np.linspace(0, 50, 50)
|
|
120
|
+
z = np.linspace(0, 25, 25)
|
|
121
|
+
|
|
122
|
+
x, y, z = np.meshgrid(x, y, z)
|
|
123
|
+
```
|
|
124
|
+
In this example we create a mesh of 50x50x25 points between 0 and 100 in x, 0 and 50 in y direction and 0 and 25 in z direction.
|
|
125
|
+
|
|
126
|
+
Then we define the random field properties:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
nb_dimensions = 3
|
|
130
|
+
mean = 10
|
|
131
|
+
variance = 2
|
|
132
|
+
vertical_scale_fluctuation = 10
|
|
133
|
+
anisotropy = [5, 5]
|
|
134
|
+
angle = [0, 0]
|
|
135
|
+
model_rf = ModelName.Gaussian
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then we create the random field:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
rf = RandomFields(model_rf, nb_dimensions, mean, variance, vertical_scale_fluctuation, anisotropy, angle, seed=14)
|
|
142
|
+
rf.generate(np.array([x.ravel(), y.ravel(), z.ravel()]).T)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
To visualise the results you can run:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
plot3D([np.array([x.ravel(), y.ravel(), z.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field_3D.png")
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
The result is the following random field:
|
|
152
|
+
|
|
153
|
+

|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.cfg
|
|
5
|
+
setup.py
|
|
6
|
+
random_fields/__init__.py
|
|
7
|
+
random_fields/__version__.py
|
|
8
|
+
random_fields/generate_field.py
|
|
9
|
+
random_fields/utils.py
|
|
10
|
+
random_fields.egg-info/PKG-INFO
|
|
11
|
+
random_fields.egg-info/SOURCES.txt
|
|
12
|
+
random_fields.egg-info/dependency_links.txt
|
|
13
|
+
random_fields.egg-info/requires.txt
|
|
14
|
+
random_fields.egg-info/top_level.txt
|
|
15
|
+
tests/test_random_field.py
|
|
16
|
+
tests/test_random_field_utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
random_fields
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = random_fields
|
|
3
|
+
version = attr: random_fields.__version__
|
|
4
|
+
author = attr: random_fields.__author__
|
|
5
|
+
description = Random fields on unstructured grids
|
|
6
|
+
long_description = file: README.md
|
|
7
|
+
long_description_content_type = text/markdown
|
|
8
|
+
classifiers =
|
|
9
|
+
Programming Language :: Python :: 3
|
|
10
|
+
License :: OSI Approved :: BSD License
|
|
11
|
+
Operating System :: Microsoft :: Windows
|
|
12
|
+
Operating System :: Unix
|
|
13
|
+
|
|
14
|
+
[options]
|
|
15
|
+
packages = random_fields
|
|
16
|
+
include_package_data = True
|
|
17
|
+
install_requires =
|
|
18
|
+
numpy>=2.0.2
|
|
19
|
+
matplotlib>=3.9.3
|
|
20
|
+
gstools>=1.6.0
|
|
21
|
+
python_requires = >=3.8
|
|
22
|
+
|
|
23
|
+
[options.extras_require]
|
|
24
|
+
testing =
|
|
25
|
+
pytest>=7.4.2
|
|
26
|
+
pytest-cov>=4.1.0
|
|
27
|
+
tox>=4.4
|
|
28
|
+
docs =
|
|
29
|
+
Sphinx==6.2.1
|
|
30
|
+
sphinx-rtd-theme==1.2.2
|
|
31
|
+
|
|
32
|
+
[egg_info]
|
|
33
|
+
tag_build =
|
|
34
|
+
tag_date = 0
|
|
35
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pytest
|
|
4
|
+
from scipy.stats import norm
|
|
5
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_model_names():
|
|
9
|
+
"test model names"
|
|
10
|
+
rf = RandomFields(ModelName.Gaussian, 2, 2, 2, 2, [2], [2])
|
|
11
|
+
assert rf.random_field_model.__name__ == 'Gaussian'
|
|
12
|
+
|
|
13
|
+
rf = RandomFields(ModelName.Exponential, 2, 2, 2, 2, [2], [2])
|
|
14
|
+
assert rf.random_field_model.__name__ == 'Exponential'
|
|
15
|
+
|
|
16
|
+
rf = RandomFields(ModelName.Matern, 2, 2, 2, 2, [2], [2])
|
|
17
|
+
assert rf.random_field_model.__name__ == 'Matern'
|
|
18
|
+
|
|
19
|
+
rf = RandomFields(ModelName.Linear, 2, 2, 2, 2, [2], [2])
|
|
20
|
+
assert rf.random_field_model.__name__ == 'Linear'
|
|
21
|
+
|
|
22
|
+
def test_failure_ndim():
|
|
23
|
+
"test failure with number of dimensions"
|
|
24
|
+
with pytest.raises(ValueError):
|
|
25
|
+
RandomFields(ModelName.Gaussian, 4, 2, 2, 2, [2], [2])
|
|
26
|
+
|
|
27
|
+
def test_model_name_failure():
|
|
28
|
+
"""test failure with wrong model name"""
|
|
29
|
+
with pytest.raises(AttributeError):
|
|
30
|
+
RandomFields(ModelName.ASD)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_distribution_RF_struc():
|
|
34
|
+
"""test distribution of 2D random field with structured mesh"""
|
|
35
|
+
nb_runs = 100
|
|
36
|
+
|
|
37
|
+
x = np.linspace(0, 100, 50)
|
|
38
|
+
y = np.linspace(0, 50, 50)
|
|
39
|
+
|
|
40
|
+
x, y = np.meshgrid(x, y)
|
|
41
|
+
|
|
42
|
+
rf_emsamble = []
|
|
43
|
+
param = []
|
|
44
|
+
for i in range(nb_runs):
|
|
45
|
+
rf = RandomFields(ModelName.Gaussian, 2, 10, 2, 1, [1], [1], seed=i)
|
|
46
|
+
rf.generate(np.array([x.ravel(), y.ravel()]).T)
|
|
47
|
+
rf_emsamble.append(rf.random_field.field)
|
|
48
|
+
mu_un, std_un = norm.fit(rf.random_field.field)
|
|
49
|
+
param.append([mu_un, std_un])
|
|
50
|
+
|
|
51
|
+
np.testing.assert_array_almost_equal(np.mean(np.array(param)[:, 0]), 10, decimal=2)
|
|
52
|
+
np.testing.assert_array_almost_equal(np.mean(np.array(param)[:, 1])**2, 2, decimal=2)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_distribution_RF_struc_3D():
|
|
56
|
+
"""test distribution of 2D random field with structured mesh"""
|
|
57
|
+
nb_runs = 100
|
|
58
|
+
|
|
59
|
+
x = np.linspace(0, 100, 20)
|
|
60
|
+
y = np.linspace(0, 40, 10)
|
|
61
|
+
z = np.linspace(0, 15, 15)
|
|
62
|
+
|
|
63
|
+
x, y, z = np.meshgrid(x, y, z)
|
|
64
|
+
|
|
65
|
+
rf_emsamble = []
|
|
66
|
+
param = []
|
|
67
|
+
for i in range(nb_runs):
|
|
68
|
+
rf = RandomFields(ModelName.Gaussian, 3, 10, 2, 1, [1, 0.5], [1, 1], seed=i)
|
|
69
|
+
rf.generate(np.array([x.ravel(), y.ravel(), z.ravel()]).T)
|
|
70
|
+
rf_emsamble.append(rf.random_field.field)
|
|
71
|
+
mu_un, std_un = norm.fit(rf.random_field.field)
|
|
72
|
+
param.append([mu_un, std_un])
|
|
73
|
+
|
|
74
|
+
np.testing.assert_array_almost_equal(np.mean(np.array(param)[:, 0]), 10, decimal=2)
|
|
75
|
+
np.testing.assert_array_almost_equal(np.mean(np.array(param)[:, 1])**2, 2, decimal=2)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_distribution_RF_unstruc():
|
|
79
|
+
"""test distribution of 2D random field with unstructured mesh"""
|
|
80
|
+
|
|
81
|
+
with open("./tests/data/mesh_2D.pickle", "rb") as fo:
|
|
82
|
+
nodes_fields = pickle.load(fo)
|
|
83
|
+
|
|
84
|
+
nb_runs = 100
|
|
85
|
+
|
|
86
|
+
x = np.array(nodes_fields)[:, 0]
|
|
87
|
+
y = np.array(nodes_fields)[:, 1]
|
|
88
|
+
|
|
89
|
+
rf_emsamble = []
|
|
90
|
+
param = []
|
|
91
|
+
for i in range(nb_runs):
|
|
92
|
+
rf = RandomFields(ModelName.Gaussian, 2, 10, 2, 0.05, [1], [1], seed=i)
|
|
93
|
+
rf.generate(np.array([x.ravel(), y.ravel()]).T)
|
|
94
|
+
rf_emsamble.append(rf.random_field.field)
|
|
95
|
+
mu_un, std_un = norm.fit(rf.random_field.field)
|
|
96
|
+
param.append([mu_un, std_un])
|
|
97
|
+
|
|
98
|
+
np.testing.assert_array_almost_equal(np.mean(np.array(param)[:, 0]), 10, decimal=2)
|
|
99
|
+
np.testing.assert_array_almost_equal(np.mean(np.array(param)[:, 1])**2, 2, decimal=2)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import pickle
|
|
6
|
+
from random_fields.generate_field import RandomFields, ModelName
|
|
7
|
+
from random_fields.utils import plot2D, plot3D
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(scope="function")
|
|
11
|
+
def cleanup_generated_files():
|
|
12
|
+
# This fixture runs before the test
|
|
13
|
+
yield
|
|
14
|
+
# This code will run after the test
|
|
15
|
+
os.remove("random_field.eps")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_distribution_RF_struc(cleanup_generated_files):
|
|
19
|
+
"""test distribution of 2D random field with structured mesh"""
|
|
20
|
+
|
|
21
|
+
x = np.linspace(0, 100, 50)
|
|
22
|
+
y = np.linspace(0, 50, 50)
|
|
23
|
+
|
|
24
|
+
x, y = np.meshgrid(x, y)
|
|
25
|
+
|
|
26
|
+
rf = RandomFields(ModelName.Gaussian, 2, 10, 2, 1, [1], [1], seed=14)
|
|
27
|
+
rf.generate(np.array([x.ravel(), y.ravel()]).T)
|
|
28
|
+
|
|
29
|
+
plot2D([np.array([x.ravel(), y.ravel()]).T], [rf.random_field], title="Random Field", output_folder="./", output_name="random_field.eps")
|
|
30
|
+
|
|
31
|
+
with open("./tests/data/random_field.eps", "r") as fi:
|
|
32
|
+
data_org = fi.read().splitlines()
|
|
33
|
+
|
|
34
|
+
with open("./random_field.eps", "r") as fi:
|
|
35
|
+
data_new = fi.read().splitlines()
|
|
36
|
+
|
|
37
|
+
header = 5
|
|
38
|
+
idx_end = data_org.index("currentfile DataString readhexstring pop")
|
|
39
|
+
data = [val == data_new[header + i] for i, val in enumerate(data_org[header:idx_end])]
|
|
40
|
+
assert all(data)
|
|
41
|
+
|
|
42
|
+
def test_distribution_RF_struc_3D(cleanup_generated_files):
|
|
43
|
+
"""test distribution of 3D random field with structured mesh"""
|
|
44
|
+
|
|
45
|
+
x = np.linspace(0, 100, 20)
|
|
46
|
+
y = np.linspace(0, 40, 10)
|
|
47
|
+
z = np.linspace(0, 15, 15)
|
|
48
|
+
|
|
49
|
+
x, y, z = np.meshgrid(x, y, z)
|
|
50
|
+
|
|
51
|
+
rf = RandomFields(ModelName.Gaussian, 3, 10, 2, 1, [1, 0.5], [1, 1], seed=14)
|
|
52
|
+
rf.generate(np.array([x.ravel(), y.ravel(), z.ravel()]).T)
|
|
53
|
+
|
|
54
|
+
plot3D([np.array([x.ravel(), y.ravel(), z.ravel()]).T], [rf.random_field],
|
|
55
|
+
title="Random Field",
|
|
56
|
+
output_folder="./",
|
|
57
|
+
output_name="random_field.eps")
|
|
58
|
+
|
|
59
|
+
if sys.platform == "win32":
|
|
60
|
+
file_test = "./tests/data/random_field_3D_windows.pickle"
|
|
61
|
+
elif sys.platform == "linux":
|
|
62
|
+
file_test = "./tests/data/random_field_3D_linux.pickle"
|
|
63
|
+
else:
|
|
64
|
+
raise Exception("Platform not supported")
|
|
65
|
+
|
|
66
|
+
with open(file_test, "rb") as fi:
|
|
67
|
+
data_test = pickle.load(fi)
|
|
68
|
+
|
|
69
|
+
assert os.path.isfile("random_field.eps")
|
|
70
|
+
assert all(rf.random_field.field == data_test)
|