andfn 0.1.3__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.
- andfn-0.1.3/LICENSE +21 -0
- andfn-0.1.3/PKG-INFO +93 -0
- andfn-0.1.3/README.md +44 -0
- andfn-0.1.3/andfn/__init__.py +30 -0
- andfn-0.1.3/andfn/bounding.py +234 -0
- andfn-0.1.3/andfn/const_head.py +242 -0
- andfn-0.1.3/andfn/dfn.py +1488 -0
- andfn-0.1.3/andfn/element.py +294 -0
- andfn-0.1.3/andfn/fracture.py +395 -0
- andfn-0.1.3/andfn/geometry_functions.py +412 -0
- andfn-0.1.3/andfn/hpc/__init__.py +3 -0
- andfn-0.1.3/andfn/hpc/hpc_bounding_circle.py +207 -0
- andfn-0.1.3/andfn/hpc/hpc_const_head_line.py +88 -0
- andfn-0.1.3/andfn/hpc/hpc_fracture.py +49 -0
- andfn-0.1.3/andfn/hpc/hpc_geometry_functions.py +392 -0
- andfn-0.1.3/andfn/hpc/hpc_intersection.py +72 -0
- andfn-0.1.3/andfn/hpc/hpc_math_functions.py +321 -0
- andfn-0.1.3/andfn/hpc/hpc_solve.py +367 -0
- andfn-0.1.3/andfn/hpc/hpc_well.py +57 -0
- andfn-0.1.3/andfn/impermeable_object.py +241 -0
- andfn-0.1.3/andfn/intersection.py +254 -0
- andfn-0.1.3/andfn/math_functions.py +236 -0
- andfn-0.1.3/andfn/well.py +174 -0
- andfn-0.1.3/andfn.egg-info/PKG-INFO +93 -0
- andfn-0.1.3/andfn.egg-info/SOURCES.txt +28 -0
- andfn-0.1.3/andfn.egg-info/dependency_links.txt +1 -0
- andfn-0.1.3/andfn.egg-info/requires.txt +6 -0
- andfn-0.1.3/andfn.egg-info/top_level.txt +1 -0
- andfn-0.1.3/pyproject.toml +48 -0
- andfn-0.1.3/setup.cfg +4 -0
andfn-0.1.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Erik Åke Ludvig Toller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
andfn-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: andfn
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: An Analytic Element Model for Discrete Fracture Networks
|
|
5
|
+
Author: Erik A.L. Toller
|
|
6
|
+
Maintainer-email: "Erik A.L. Toller" <erik@toller.se>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2024 Erik Åke Ludvig Toller
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/eriktoller/andfn
|
|
30
|
+
Project-URL: Documentation, https://github.com/eriktoller/andfn
|
|
31
|
+
Project-URL: Repository, https://github.com/eriktoller/andfn
|
|
32
|
+
Project-URL: Issues, https://github.com/eriktoller/andfn/issues
|
|
33
|
+
Keywords: hydrology,groundwater,model,analytic element method,discrete fracture network,dfn
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: Intended Audience :: Other Audience
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Programming Language :: Python
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
40
|
+
Requires-Python: >=3.8
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: numpy
|
|
44
|
+
Requires-Dist: matplotlib
|
|
45
|
+
Requires-Dist: pyvista
|
|
46
|
+
Requires-Dist: scipy
|
|
47
|
+
Requires-Dist: numba
|
|
48
|
+
Requires-Dist: h5py
|
|
49
|
+
|
|
50
|
+
# AnDFN, Analytical Discrete Fracture Network
|
|
51
|
+
|
|
52
|
+
## Introduction
|
|
53
|
+
AnDFN is a computer program for the modelling of groundwater flow in a discrete fracture network (DFN). The program is based on the Analytic Element Method (AEM) and is distributed as a Python package with various modules and scripts. The original development of this code was funded by BeFo (Stiftelsen Bergteknisk Forskning).
|
|
54
|
+
|
|
55
|
+
## Instalation
|
|
56
|
+
AnDFN can be installed from PyPi.
|
|
57
|
+
|
|
58
|
+
Installation:
|
|
59
|
+
```
|
|
60
|
+
pip install andfn
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Update:
|
|
64
|
+
```
|
|
65
|
+
pip install andfn --upgrade
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Uninstall
|
|
69
|
+
```
|
|
70
|
+
pip uninstall andfn
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Dependencies
|
|
74
|
+
AnDFN requires the following Python packages:
|
|
75
|
+
- Numpy
|
|
76
|
+
- Pyvista
|
|
77
|
+
- Matplotlib
|
|
78
|
+
- Numba
|
|
79
|
+
- h5py (for load and save)
|
|
80
|
+
|
|
81
|
+
## Functionality
|
|
82
|
+
AnDFN currently have the following functionality:
|
|
83
|
+
- Generate random DFN
|
|
84
|
+
- Compute the intersections of a DFN
|
|
85
|
+
- Solve the AEM model for a DFN
|
|
86
|
+
- Plot the flow net for the AEM model
|
|
87
|
+
- Import DFNs
|
|
88
|
+
- Load and save DFNs
|
|
89
|
+
|
|
90
|
+
## Citation
|
|
91
|
+
The basic theory for this program is published in:
|
|
92
|
+
|
|
93
|
+
Otto D.L. Strack, Erik A.L. Toller, An analytic element model for flow in fractured impermeable rock, *Journal of Hydrology*, 2024, 131983, ISSN 0022-1694, https://doi.org/10.1016/j.jhydrol.2024.131983.
|
andfn-0.1.3/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# AnDFN, Analytical Discrete Fracture Network
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
AnDFN is a computer program for the modelling of groundwater flow in a discrete fracture network (DFN). The program is based on the Analytic Element Method (AEM) and is distributed as a Python package with various modules and scripts. The original development of this code was funded by BeFo (Stiftelsen Bergteknisk Forskning).
|
|
5
|
+
|
|
6
|
+
## Instalation
|
|
7
|
+
AnDFN can be installed from PyPi.
|
|
8
|
+
|
|
9
|
+
Installation:
|
|
10
|
+
```
|
|
11
|
+
pip install andfn
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Update:
|
|
15
|
+
```
|
|
16
|
+
pip install andfn --upgrade
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Uninstall
|
|
20
|
+
```
|
|
21
|
+
pip uninstall andfn
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Dependencies
|
|
25
|
+
AnDFN requires the following Python packages:
|
|
26
|
+
- Numpy
|
|
27
|
+
- Pyvista
|
|
28
|
+
- Matplotlib
|
|
29
|
+
- Numba
|
|
30
|
+
- h5py (for load and save)
|
|
31
|
+
|
|
32
|
+
## Functionality
|
|
33
|
+
AnDFN currently have the following functionality:
|
|
34
|
+
- Generate random DFN
|
|
35
|
+
- Compute the intersections of a DFN
|
|
36
|
+
- Solve the AEM model for a DFN
|
|
37
|
+
- Plot the flow net for the AEM model
|
|
38
|
+
- Import DFNs
|
|
39
|
+
- Load and save DFNs
|
|
40
|
+
|
|
41
|
+
## Citation
|
|
42
|
+
The basic theory for this program is published in:
|
|
43
|
+
|
|
44
|
+
Otto D.L. Strack, Erik A.L. Toller, An analytic element model for flow in fractured impermeable rock, *Journal of Hydrology*, 2024, 131983, ISSN 0022-1694, https://doi.org/10.1016/j.jhydrol.2024.131983.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Copyright (C), 2024, Erik A. L. Toller.
|
|
2
|
+
|
|
3
|
+
AnDFN is a computer program that calculated the flow in a discrete fracture network (DFN) using the Analytic Element
|
|
4
|
+
Method (AEM).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# version number
|
|
8
|
+
__name__ = "andfn"
|
|
9
|
+
__author__ = "Erik A.L. Toller"
|
|
10
|
+
__version__ = "0.1.3"
|
|
11
|
+
|
|
12
|
+
# Import all classes and functions
|
|
13
|
+
from andfn.bounding import BoundingCircle
|
|
14
|
+
from andfn.const_head import ConstantHeadLine
|
|
15
|
+
from andfn.dfn import DFN
|
|
16
|
+
from andfn.fracture import Fracture
|
|
17
|
+
from andfn.intersection import Intersection
|
|
18
|
+
from andfn.well import Well
|
|
19
|
+
from andfn.impermeable_object import ImpermeableCircle
|
|
20
|
+
import andfn.geometry_functions
|
|
21
|
+
import andfn.math_functions
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
'BoundingCircle',
|
|
25
|
+
'ConstantHeadLine',
|
|
26
|
+
'DFN',
|
|
27
|
+
'Fracture',
|
|
28
|
+
'Intersection',
|
|
29
|
+
'Well'
|
|
30
|
+
]
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Notes
|
|
3
|
+
-----
|
|
4
|
+
This module contains the bounding classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import andfn.math_functions as mf
|
|
9
|
+
import andfn.geometry_functions as gf
|
|
10
|
+
import numpy as np
|
|
11
|
+
from andfn.well import Well
|
|
12
|
+
from andfn.intersection import Intersection
|
|
13
|
+
from andfn.const_head import ConstantHeadLine
|
|
14
|
+
from .element import Element
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BoundingCircle(Element):
|
|
18
|
+
def __init__(self, label, radius, frac0, ncoef=5, nint=10, **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
Initializes the bounding circle class.
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
label : str or int
|
|
24
|
+
The label of the bounding circle.
|
|
25
|
+
r : float
|
|
26
|
+
The radius of the bounding circle.
|
|
27
|
+
ncoef : int
|
|
28
|
+
The number of coefficients in the asymptotic expansion.
|
|
29
|
+
nint : int
|
|
30
|
+
The number of integration points.
|
|
31
|
+
frac : Fracture
|
|
32
|
+
The fracture object that the bounding circle is associated with.
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(label, id_=0, type_=1)
|
|
35
|
+
self.label = label
|
|
36
|
+
self.radius = radius
|
|
37
|
+
self.ncoef = ncoef
|
|
38
|
+
self.nint = nint
|
|
39
|
+
self.frac0 = frac0
|
|
40
|
+
|
|
41
|
+
# Create the pre-calculation variables
|
|
42
|
+
self.thetas = np.linspace(start=0, stop=2 * np.pi, num=nint, endpoint=False)
|
|
43
|
+
self.z_theta = gf.map_chi_to_z_circle(np.exp(1j * self.thetas), self.radius)
|
|
44
|
+
self.coef = np.zeros(ncoef, dtype=complex)
|
|
45
|
+
|
|
46
|
+
# Correction to the stream function
|
|
47
|
+
self.sign_array = None
|
|
48
|
+
self.discharge_element = None
|
|
49
|
+
self.element_pos = None
|
|
50
|
+
self.dpsi_corr = np.zeros(self.nint - 1, dtype=float)
|
|
51
|
+
|
|
52
|
+
# Set the kwargs
|
|
53
|
+
for key, value in kwargs.items():
|
|
54
|
+
setattr(self, key, value)
|
|
55
|
+
|
|
56
|
+
def get_chi(self, z):
|
|
57
|
+
"""
|
|
58
|
+
Get the chi for the bounding circle.
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
z : complex | np.ndarray
|
|
62
|
+
A point in the complex z plane.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
chi : complex | np.ndarray
|
|
67
|
+
The complex chi for the bounding circle.
|
|
68
|
+
"""
|
|
69
|
+
chi = gf.map_z_circle_to_chi(z, self.radius)
|
|
70
|
+
if isinstance(chi, np.ndarray) and len(chi) > 1:
|
|
71
|
+
chi[np.abs(chi) > 1.0 + 1e-10] = np.nan + 1j * np.nan
|
|
72
|
+
else:
|
|
73
|
+
if np.abs(chi) > 1.0 + 1e-10:
|
|
74
|
+
chi = np.nan + 1j * np.nan
|
|
75
|
+
return chi
|
|
76
|
+
|
|
77
|
+
def calc_omega(self, z):
|
|
78
|
+
"""
|
|
79
|
+
Calculates the omega for the bounding circle.
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
z : complex | np.ndarray
|
|
83
|
+
A point in the complex z plane.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
omega : complex
|
|
88
|
+
The complex potential for the bounding circle.
|
|
89
|
+
"""
|
|
90
|
+
chi = self.get_chi(z)
|
|
91
|
+
omega = mf.taylor_series(chi, self.coef)
|
|
92
|
+
return omega
|
|
93
|
+
|
|
94
|
+
def calc_w(self, z):
|
|
95
|
+
"""
|
|
96
|
+
Calculates the complex discharge vector for the bounding circle.
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
z : complex
|
|
100
|
+
A point in the complex z plane.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
w : complex
|
|
105
|
+
The complex discharge vector for the bounding circle.
|
|
106
|
+
"""
|
|
107
|
+
chi = self.get_chi(z)
|
|
108
|
+
w = -mf.taylor_series_d1(chi, self.coef)
|
|
109
|
+
w /= self.radius
|
|
110
|
+
return w
|
|
111
|
+
|
|
112
|
+
def find_branch_cuts(self):
|
|
113
|
+
"""
|
|
114
|
+
Find the branch cuts for the fracture.
|
|
115
|
+
"""
|
|
116
|
+
#TODO: Vectorize this function and only do the calculations once to find the index of the branch cuts.
|
|
117
|
+
|
|
118
|
+
# Find the branch cuts
|
|
119
|
+
z_pos = gf.map_chi_to_z_circle(np.exp(1j * self.thetas), self.radius)
|
|
120
|
+
self.dpsi_corr = np.zeros(self.nint - 1, dtype=float)
|
|
121
|
+
element_pos = []
|
|
122
|
+
discharge_element = []
|
|
123
|
+
sign_array = []
|
|
124
|
+
|
|
125
|
+
#for e in self.frac0.elements:
|
|
126
|
+
for ii in range(self.nint - 1):
|
|
127
|
+
for e in self.frac0.elements:
|
|
128
|
+
if isinstance(e, Well):
|
|
129
|
+
# Find the branch cut for the well
|
|
130
|
+
chi0 = gf.map_z_circle_to_chi(z_pos[ii], e.radius, e.center)
|
|
131
|
+
chi1 = gf.map_z_circle_to_chi(z_pos[ii + 1], e.radius, e.center)
|
|
132
|
+
if np.sign(np.imag(chi0)) != np.sign(np.imag(chi1)) and np.real(chi0) < 0:
|
|
133
|
+
self.dpsi_corr[ii] -= e.q
|
|
134
|
+
element_pos.append(ii)
|
|
135
|
+
discharge_element.append(e)
|
|
136
|
+
sign_array.append(-1)
|
|
137
|
+
elif isinstance(e, ConstantHeadLine):
|
|
138
|
+
# Find the branch cut for the constant head line
|
|
139
|
+
chi0 = gf.map_z_line_to_chi(z_pos[ii], e.endpoints0)
|
|
140
|
+
chi1 = gf.map_z_line_to_chi(z_pos[ii + 1], e.endpoints0)
|
|
141
|
+
if np.sign(np.imag(chi0)) != np.sign(np.imag(chi1)) and np.real(chi0) < 0:
|
|
142
|
+
self.dpsi_corr[ii] -= e.q
|
|
143
|
+
element_pos.append(ii)
|
|
144
|
+
discharge_element.append(e)
|
|
145
|
+
sign_array.append(-1)
|
|
146
|
+
elif isinstance(e, Intersection):
|
|
147
|
+
# Find the branch cut for the intersection
|
|
148
|
+
if e.frac0 == self.frac0:
|
|
149
|
+
chi0 = gf.map_z_line_to_chi(z_pos[ii], e.endpoints0)
|
|
150
|
+
chi1 = gf.map_z_line_to_chi(z_pos[ii + 1], e.endpoints0)
|
|
151
|
+
ln0 = np.imag(np.log(chi0))
|
|
152
|
+
ln1 = np.imag(np.log(chi1))
|
|
153
|
+
if np.sign(ln0) != np.sign(ln1) and np.abs(ln0) + np.abs(ln1) > np.pi:
|
|
154
|
+
self.dpsi_corr[ii] -= e.q
|
|
155
|
+
element_pos.append(ii)
|
|
156
|
+
discharge_element.append(e)
|
|
157
|
+
sign_array.append(-1)
|
|
158
|
+
else:
|
|
159
|
+
chi0 = gf.map_z_line_to_chi(z_pos[ii], e.endpoints1)
|
|
160
|
+
chi1 = gf.map_z_line_to_chi(z_pos[ii + 1], e.endpoints1)
|
|
161
|
+
ln0 = np.imag(np.log(chi0))
|
|
162
|
+
ln1 = np.imag(np.log(chi1))
|
|
163
|
+
if np.sign(ln0) != np.sign(ln1) and np.abs(ln0) + np.abs(ln1) > np.pi:
|
|
164
|
+
self.dpsi_corr[ii] += e.q
|
|
165
|
+
element_pos.append(ii)
|
|
166
|
+
discharge_element.append(e)
|
|
167
|
+
sign_array.append(1)
|
|
168
|
+
|
|
169
|
+
self.sign_array = sign_array
|
|
170
|
+
self.discharge_element = discharge_element
|
|
171
|
+
self.element_pos = element_pos
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
dpsi_corr2 = np.zeros(self.nint - 1, dtype=float)
|
|
175
|
+
for i, e in enumerate(discharge_element):
|
|
176
|
+
dpsi_corr2[element_pos[i]] += e.q*sign_array[i]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_dpsi_corr(self):
|
|
180
|
+
"""
|
|
181
|
+
Get the correction to the stream function for the branch cuts.
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
np.ndarray
|
|
185
|
+
The correction to the stream function.
|
|
186
|
+
"""
|
|
187
|
+
if self.sign_array is None:
|
|
188
|
+
self.find_branch_cuts()
|
|
189
|
+
dpsi_corr2 = np.zeros(self.nint - 1, dtype=float)
|
|
190
|
+
for i, e in enumerate(self.discharge_element):
|
|
191
|
+
dpsi_corr2[self.element_pos[i]] += e.q * self.sign_array[i]
|
|
192
|
+
self.dpsi_corr = dpsi_corr2
|
|
193
|
+
|
|
194
|
+
def solve(self):
|
|
195
|
+
"""
|
|
196
|
+
Solve the coefficients of the bounding circle.
|
|
197
|
+
"""
|
|
198
|
+
#self.find_branch_cuts() # Find the branch cuts
|
|
199
|
+
self.get_dpsi_corr() # Get the correction to the stream function
|
|
200
|
+
s = mf.cauchy_integral_domega(self.nint, self.ncoef, self.thetas, self.dpsi_corr,
|
|
201
|
+
lambda z: self.frac0.calc_omega(z, exclude=self),
|
|
202
|
+
lambda chi: gf.map_chi_to_z_circle(chi, self.radius))
|
|
203
|
+
|
|
204
|
+
self.error = np.max(np.abs(s + self.coef))
|
|
205
|
+
self.coef = -s
|
|
206
|
+
|
|
207
|
+
def check_boundary_condition(self, n=10):
|
|
208
|
+
"""
|
|
209
|
+
Check if the bounding circle satisfies the boundary conditions.
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
n : int
|
|
213
|
+
The number of points to check the boundary condition at.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
float
|
|
218
|
+
The error in the boundary condition.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
# Calculate the stream function on the boundary of the fracture
|
|
222
|
+
theta = np.linspace(0, 2 * np.pi, n, endpoint=True)
|
|
223
|
+
z0 = gf.map_chi_to_z_circle(np.exp(1j * theta), self.radius)
|
|
224
|
+
omega0 = self.frac0.calc_omega(z0, exclude=None)
|
|
225
|
+
psi = np.imag(omega0)
|
|
226
|
+
dpsi = np.diff(psi)
|
|
227
|
+
q = np.sum(np.abs(self.dpsi_corr))
|
|
228
|
+
mean_dpsi = np.abs(np.max(dpsi) - np.min(dpsi))
|
|
229
|
+
#if mean_dpsi > q/2:
|
|
230
|
+
# mean_dpsi = np.abs(np.abs(np.max(dpsi) - np.min(dpsi)) - q)
|
|
231
|
+
#if q < 1e-10:
|
|
232
|
+
# return np.abs(np.max(psi) - np.min(psi))
|
|
233
|
+
#return mean_dpsi / q
|
|
234
|
+
return mean_dpsi / np.max(dpsi)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Notes
|
|
3
|
+
-----
|
|
4
|
+
This module contains the constant head classes.
|
|
5
|
+
"""
|
|
6
|
+
from . import math_functions as mf
|
|
7
|
+
from . import geometry_functions as gf
|
|
8
|
+
import numpy as np
|
|
9
|
+
from .element import Element
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConstantHeadLine(Element):
|
|
13
|
+
def __init__(self, label, endpoints0, head, frac0, ncoef=5, nint=10, **kwargs):
|
|
14
|
+
"""
|
|
15
|
+
Constructor for the constant head line element.
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
label : str
|
|
19
|
+
The label of the constant head line
|
|
20
|
+
endpoints0 : np.ndarray
|
|
21
|
+
The endpoints of the constant head line
|
|
22
|
+
head : float
|
|
23
|
+
The head of the constant head line
|
|
24
|
+
frac0 : Fracture
|
|
25
|
+
The fracture that the constant head line is associated with
|
|
26
|
+
ncoef : int
|
|
27
|
+
The number of coefficients in the asymptotic expansion
|
|
28
|
+
nint : int
|
|
29
|
+
The number of integration points in the asymptotic expansion
|
|
30
|
+
kwargs : dict
|
|
31
|
+
Additional keyword arguments
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(label, id_=0, type_=3)
|
|
34
|
+
self.label = label
|
|
35
|
+
self.endpoints0 = endpoints0
|
|
36
|
+
self.ncoef = ncoef
|
|
37
|
+
self.nint = nint
|
|
38
|
+
self.q = 0.0
|
|
39
|
+
self.head = head
|
|
40
|
+
self.frac0 = frac0
|
|
41
|
+
|
|
42
|
+
# Create the pre-calculation variables
|
|
43
|
+
self.thetas = np.linspace(start=np.pi/(2*nint), stop=np.pi + np.pi/(2*nint), num=nint, endpoint=False)
|
|
44
|
+
self.coef = np.zeros(ncoef, dtype=complex)
|
|
45
|
+
self.phi = frac0.phi_from_head(head)
|
|
46
|
+
|
|
47
|
+
# Set the kwargs
|
|
48
|
+
for key, value in kwargs.items():
|
|
49
|
+
setattr(self, key, value)
|
|
50
|
+
|
|
51
|
+
# Assign to the fracture
|
|
52
|
+
self.frac0.add_element(self)
|
|
53
|
+
|
|
54
|
+
def discharge_term(self, z):
|
|
55
|
+
"""
|
|
56
|
+
Calculate the discharge term for the constant head line.
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
z : np.ndarray
|
|
60
|
+
The points to calculate the discharge term at
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
float
|
|
65
|
+
The discharge term
|
|
66
|
+
"""
|
|
67
|
+
chi = gf.map_z_line_to_chi(z, self.endpoints0)
|
|
68
|
+
return np.sum(np.real(mf.well_chi(chi, 1))) / len(z)
|
|
69
|
+
|
|
70
|
+
def update_head(self, head):
|
|
71
|
+
"""
|
|
72
|
+
Update the head of the constant head line.
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
head : float
|
|
76
|
+
The new head of the constant head line--
|
|
77
|
+
"""
|
|
78
|
+
self.head = head
|
|
79
|
+
self.phi = self.frac0.phi_from_head(head)
|
|
80
|
+
|
|
81
|
+
def length(self):
|
|
82
|
+
"""
|
|
83
|
+
Calculate the length of the constant head line.
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
float
|
|
87
|
+
The length of the constant head line
|
|
88
|
+
"""
|
|
89
|
+
return np.abs(self.endpoints0[0] - self.endpoints0[1])
|
|
90
|
+
|
|
91
|
+
def z_array(self, n):
|
|
92
|
+
"""
|
|
93
|
+
Create an array of z points along the constant head line.
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
n : int
|
|
97
|
+
The number of points to create
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
np.ndarray
|
|
102
|
+
The array of z points
|
|
103
|
+
"""
|
|
104
|
+
return np.linspace(self.endpoints0[0], self.endpoints0[1], n)
|
|
105
|
+
|
|
106
|
+
def omega_along_element(self, n, frac_is):
|
|
107
|
+
"""
|
|
108
|
+
Calculate the omega along the constant head line.
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
n : int
|
|
112
|
+
The number of points to calculate the omega at
|
|
113
|
+
frac_is : Fracture
|
|
114
|
+
The fracture that the calculation is being done for
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
np.ndarray
|
|
119
|
+
The complex discharge potential along the constant head line
|
|
120
|
+
"""
|
|
121
|
+
z = self.z_array(n)
|
|
122
|
+
omega = frac_is.calc_omega(z)
|
|
123
|
+
return omega
|
|
124
|
+
|
|
125
|
+
def z_array_tracking(self, n, offset=1e-3):
|
|
126
|
+
"""
|
|
127
|
+
Create an array of z points along the constant head line with an offset.
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
n : int
|
|
131
|
+
The number of points to create
|
|
132
|
+
offset : float
|
|
133
|
+
The offset to use
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
np.ndarray
|
|
138
|
+
The array of z points
|
|
139
|
+
"""
|
|
140
|
+
chi = np.exp(1j * np.linspace(0, 2*np.pi, n, endpoint=False))*(1+offset)
|
|
141
|
+
return gf.map_chi_to_z_line(chi, self.endpoints0)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def calc_omega(self, z):
|
|
145
|
+
"""
|
|
146
|
+
Calculate the complex discharge potential for the constant head line.
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
z : np.ndarray
|
|
150
|
+
The points to calculate the complex discharge potential at
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
np.ndarray
|
|
154
|
+
The complex discharge potential
|
|
155
|
+
"""
|
|
156
|
+
# Map the z point to the chi plane
|
|
157
|
+
chi = gf.map_z_line_to_chi(z, self.endpoints0)
|
|
158
|
+
# Calculate omega
|
|
159
|
+
omega = mf.asym_expansion(chi, self.coef) + mf.well_chi(chi, self.q)
|
|
160
|
+
return omega
|
|
161
|
+
|
|
162
|
+
def calc_w(self, z):
|
|
163
|
+
"""
|
|
164
|
+
Calculate the complex discharge vector for the constant head line.
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
z : np.ndarray
|
|
168
|
+
The points to calculate the complex discharge vector at
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
np.ndarray
|
|
172
|
+
The complex discharge vector
|
|
173
|
+
"""
|
|
174
|
+
# Map the z point to the chi plane
|
|
175
|
+
chi = gf.map_z_line_to_chi(z, self.endpoints0)
|
|
176
|
+
# Calculate w
|
|
177
|
+
w = -mf.asym_expansion_d1(chi, self.coef) - self.q / (2 * np.pi * chi)
|
|
178
|
+
w *= 2 * chi ** 2 / (chi ** 2 - 1) * 2 / (self.endpoints0[1] - self.endpoints0[0])
|
|
179
|
+
return w
|
|
180
|
+
|
|
181
|
+
def solve(self):
|
|
182
|
+
"""
|
|
183
|
+
Solve the coefficients of the constant head line.
|
|
184
|
+
"""
|
|
185
|
+
s = mf.cauchy_integral_real(self.nint, self.ncoef, self.thetas,
|
|
186
|
+
lambda z: self.frac0.calc_omega(z, exclude=self),
|
|
187
|
+
lambda chi: gf.map_chi_to_z_line(chi, self.endpoints0))
|
|
188
|
+
|
|
189
|
+
s = np.real(s)
|
|
190
|
+
s[0] = 0 # Set the first coefficient to zero (constant embedded in discharge matrix)
|
|
191
|
+
|
|
192
|
+
self.error = np.max(np.abs(s + self.coef))
|
|
193
|
+
self.coef = -s
|
|
194
|
+
|
|
195
|
+
def check_boundary_condition(self, n=10):
|
|
196
|
+
"""
|
|
197
|
+
Check if the constant head line satisfies the boundary conditions.
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
n : int
|
|
201
|
+
The number of points to check the boundary condition at
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
float
|
|
205
|
+
The error in the boundary condition
|
|
206
|
+
"""
|
|
207
|
+
chi = np.exp(1j * np.linspace(0, np.pi, n))
|
|
208
|
+
# Calculate the head in fracture 0
|
|
209
|
+
z0 = gf.map_chi_to_z_line(chi, self.endpoints0)
|
|
210
|
+
omega0 = self.frac0.calc_omega(z0, exclude=None)
|
|
211
|
+
|
|
212
|
+
return np.mean(np.abs(self.phi - np.real(omega0))) / np.abs(self.phi)
|
|
213
|
+
|
|
214
|
+
def check_chi_crossing(self, z0, z1, atol=1e-10):
|
|
215
|
+
"""
|
|
216
|
+
Check the line between two points crosses the constant head line.
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
z0 : complex
|
|
220
|
+
The first point
|
|
221
|
+
z1 : complex
|
|
222
|
+
The second point
|
|
223
|
+
atol : float
|
|
224
|
+
The absolute tolerance for the check
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
complex | bool
|
|
229
|
+
The intersection point if it exists, otherwise False
|
|
230
|
+
"""
|
|
231
|
+
z = gf.line_line_intersection(z0, z1, self.endpoints0[0], self.endpoints0[1])
|
|
232
|
+
|
|
233
|
+
if z is None:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
if np.abs(np.abs(z - z0) + np.abs(z1 - z) - np.abs(z0 - z1)) > atol:
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
if np.abs(np.abs(z - self.endpoints0[0]) + np.abs(z - self.endpoints0[1]) - np.abs(self.endpoints0[0] - self.endpoints0[1])) > atol:
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
return z
|