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 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