capytaine 2.2.1__cp311-cp311-win_amd64.whl → 2.3.1__cp311-cp311-win_amd64.whl
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.
- capytaine/__about__.py +1 -1
- capytaine/__init__.py +5 -4
- capytaine/bem/airy_waves.py +7 -2
- capytaine/bem/problems_and_results.py +91 -39
- capytaine/bem/solver.py +128 -40
- capytaine/bodies/bodies.py +46 -18
- capytaine/bodies/predefined/rectangles.py +2 -0
- capytaine/green_functions/FinGreen3D/.gitignore +1 -0
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
- capytaine/green_functions/FinGreen3D/LICENSE +165 -0
- capytaine/green_functions/FinGreen3D/Makefile +16 -0
- capytaine/green_functions/FinGreen3D/README.md +24 -0
- capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
- capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
- capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
- capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
- capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
- capytaine/green_functions/abstract_green_function.py +55 -3
- capytaine/green_functions/delhommeau.py +205 -130
- capytaine/green_functions/hams.py +204 -0
- capytaine/green_functions/libs/Delhommeau_float32.cp311-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/Delhommeau_float32.cp311-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp311-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp311-win_amd64.pyd +0 -0
- capytaine/io/bemio.py +14 -2
- capytaine/io/mesh_loaders.py +1 -1
- capytaine/io/wamit.py +479 -0
- capytaine/io/xarray.py +261 -117
- capytaine/matrices/linear_solvers.py +1 -1
- capytaine/meshes/clipper.py +1 -0
- capytaine/meshes/collections.py +19 -1
- capytaine/meshes/mesh_like_protocol.py +37 -0
- capytaine/meshes/meshes.py +28 -8
- capytaine/meshes/symmetric.py +89 -10
- capytaine/post_pro/kochin.py +4 -4
- capytaine/tools/lists_of_points.py +3 -3
- capytaine/tools/prony_decomposition.py +60 -4
- capytaine/tools/symbolic_multiplication.py +30 -4
- capytaine/tools/timer.py +66 -0
- capytaine-2.3.1.dist-info/DELVEWHEEL +2 -0
- {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/METADATA +6 -10
- {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/RECORD +47 -31
- capytaine-2.2.1.dist-info/DELVEWHEEL +0 -2
- {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/LICENSE +0 -0
- {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/WHEEL +0 -0
- {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
BUILD_DIR=build
|
|
2
|
+
|
|
3
|
+
run_test: $(BUILD_DIR)/test_program
|
|
4
|
+
$(BUILD_DIR)/test_program
|
|
5
|
+
|
|
6
|
+
$(BUILD_DIR)/test_program: test_program.f90 $(BUILD_DIR)/LiangWuNoblesseWaveTerm.o
|
|
7
|
+
mkdir -p $(BUILD_DIR)
|
|
8
|
+
gfortran -fopenmp $^ -o $@ -J$(BUILD_DIR)
|
|
9
|
+
|
|
10
|
+
$(BUILD_DIR)/LiangWuNoblesseWaveTerm.o: LiangWuNoblesseWaveTerm.f90
|
|
11
|
+
mkdir -p $(BUILD_DIR)
|
|
12
|
+
gfortran -c $< -o $@ -J$(BUILD_DIR)
|
|
13
|
+
|
|
14
|
+
clean:
|
|
15
|
+
rm -rf $(BUILD_DIR)
|
|
16
|
+
.PHONY: run_test clean
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
! Just test that the library compiles and run
|
|
2
|
+
|
|
3
|
+
program test
|
|
4
|
+
use LiangWuNoblesseWaveTerm, only: HavelockGF
|
|
5
|
+
implicit none
|
|
6
|
+
|
|
7
|
+
integer, parameter :: n = 10
|
|
8
|
+
|
|
9
|
+
real(8), dimension(n) :: r, z
|
|
10
|
+
complex(8), dimension(n) :: gf, gf_r
|
|
11
|
+
integer :: i
|
|
12
|
+
|
|
13
|
+
do i = 1, n
|
|
14
|
+
r(i) = real(i, kind=8)/n
|
|
15
|
+
end do
|
|
16
|
+
|
|
17
|
+
do i = 1, n
|
|
18
|
+
z(i) = -real(i, kind=8)/n
|
|
19
|
+
end do
|
|
20
|
+
|
|
21
|
+
!$OMP PARALLEL DO PRIVATE(i)
|
|
22
|
+
do i = 1, n
|
|
23
|
+
call HavelockGF(r(i), z(i), gf(i), gf_r(i))
|
|
24
|
+
end do
|
|
25
|
+
|
|
26
|
+
print*, real(gf(:))
|
|
27
|
+
|
|
28
|
+
end program test
|
|
@@ -1,12 +1,64 @@
|
|
|
1
1
|
"""Abstract structure of a class used to compute the Green function"""
|
|
2
|
-
# Copyright (C) 2017-
|
|
3
|
-
# See LICENSE file at <https://github.com/
|
|
2
|
+
# Copyright (C) 2017-2024 Matthieu Ancellin
|
|
3
|
+
# See LICENSE file at <https://github.com/capytaine/capytaine>
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from capytaine.meshes.mesh_like_protocol import MeshLike
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GreenFunctionEvaluationError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
7
16
|
class AbstractGreenFunction(ABC):
|
|
8
17
|
"""Abstract method to evaluate the Green function."""
|
|
9
18
|
|
|
19
|
+
floating_point_precision: str
|
|
20
|
+
|
|
21
|
+
def _get_colocation_points_and_normals(self, mesh1, mesh2, adjoint_double_layer):
|
|
22
|
+
if isinstance(mesh1, MeshLike):
|
|
23
|
+
collocation_points = mesh1.faces_centers
|
|
24
|
+
nb_collocation_points = mesh1.nb_faces
|
|
25
|
+
if not adjoint_double_layer: # Computing the D matrix
|
|
26
|
+
early_dot_product_normals = mesh2.faces_normals
|
|
27
|
+
else: # Computing the K matrix
|
|
28
|
+
early_dot_product_normals = mesh1.faces_normals
|
|
29
|
+
|
|
30
|
+
elif isinstance(mesh1, np.ndarray) and mesh1.ndim == 2 and mesh1.shape[1] == 3:
|
|
31
|
+
# This is used when computing potential or velocity at given points in postprocessing
|
|
32
|
+
collocation_points = mesh1
|
|
33
|
+
nb_collocation_points = mesh1.shape[0]
|
|
34
|
+
if not adjoint_double_layer: # Computing the D matrix
|
|
35
|
+
early_dot_product_normals = mesh2.faces_normals
|
|
36
|
+
else: # Computing the K matrix
|
|
37
|
+
early_dot_product_normals = np.zeros((nb_collocation_points, 3))
|
|
38
|
+
# Dummy argument since this method is meant to be used either
|
|
39
|
+
# - to compute potential, then only S is needed and early_dot_product_normals is irrelevant,
|
|
40
|
+
# - to compute velocity, then the adjoint full gradient is needed and early_dot_product is False and this value is unused.
|
|
41
|
+
# TODO: add an only_S argument and return an error here if (early_dot_product and not only_S)
|
|
42
|
+
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError(f"Unrecognized first input for {self.__class__.__name__}.evaluate:\n{mesh1}")
|
|
45
|
+
|
|
46
|
+
return collocation_points, early_dot_product_normals
|
|
47
|
+
|
|
48
|
+
def _init_matrices(self, shape, early_dot_product):
|
|
49
|
+
if self.floating_point_precision == "float32":
|
|
50
|
+
dtype = "complex64"
|
|
51
|
+
elif self.floating_point_precision == "float64":
|
|
52
|
+
dtype = "complex128"
|
|
53
|
+
else:
|
|
54
|
+
raise NotImplementedError(
|
|
55
|
+
f"Unsupported floating point precision: {self.floating_point_precision}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
S = np.zeros(shape, order="F", dtype=dtype)
|
|
59
|
+
K = np.zeros((shape[0], shape[1], 1 if early_dot_product else 3), order="F", dtype=dtype)
|
|
60
|
+
return S, K
|
|
61
|
+
|
|
10
62
|
@abstractmethod
|
|
11
|
-
def evaluate(self, mesh1, mesh2, free_surface,
|
|
63
|
+
def evaluate(self, mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer=True, early_dot_product=True):
|
|
12
64
|
pass
|
|
@@ -9,12 +9,10 @@ from importlib import import_module
|
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
|
-
from capytaine.
|
|
13
|
-
from capytaine.meshes.collections import CollectionOfMeshes
|
|
14
|
-
from capytaine.tools.prony_decomposition import exponential_decomposition, error_exponential_decomposition
|
|
12
|
+
from capytaine.tools.prony_decomposition import find_best_exponential_decomposition, PronyDecompositionFailure
|
|
15
13
|
from capytaine.tools.cache_on_disk import cache_directory
|
|
16
14
|
|
|
17
|
-
from capytaine.green_functions.abstract_green_function import AbstractGreenFunction
|
|
15
|
+
from capytaine.green_functions.abstract_green_function import AbstractGreenFunction, GreenFunctionEvaluationError
|
|
18
16
|
|
|
19
17
|
LOG = logging.getLogger(__name__)
|
|
20
18
|
|
|
@@ -25,7 +23,8 @@ _default_parameters = dict(
|
|
|
25
23
|
tabulation_zmin=-251.0,
|
|
26
24
|
tabulation_nb_integration_points=1001,
|
|
27
25
|
tabulation_grid_shape="scaled_nemoh3",
|
|
28
|
-
|
|
26
|
+
finite_depth_method="newer",
|
|
27
|
+
finite_depth_prony_decomposition_method="python",
|
|
29
28
|
floating_point_precision="float64",
|
|
30
29
|
gf_singularities="low_freq",
|
|
31
30
|
)
|
|
@@ -66,6 +65,8 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
66
65
|
Default: calls capytaine.tools.cache_on_disk.cache_directory(), which
|
|
67
66
|
returns the value of the environment variable CAPYTAINE_CACHE_DIR if
|
|
68
67
|
set, or else the default cache directory on your system.
|
|
68
|
+
finite_depth_method: string, optional
|
|
69
|
+
The method used to compute the finite depth Green function.
|
|
69
70
|
finite_depth_prony_decomposition_method: string, optional
|
|
70
71
|
The implementation of the Prony decomposition used to compute the
|
|
71
72
|
finite water_depth Green function. Accepted values: :code:`'fortran'`
|
|
@@ -88,7 +89,9 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
88
89
|
Compiled Fortran module with functions used to compute the Green
|
|
89
90
|
function.
|
|
90
91
|
tabulation_grid_shape_index: int
|
|
91
|
-
|
|
92
|
+
gf_singularities_index: int
|
|
93
|
+
finite_depth_method_index: int
|
|
94
|
+
Integers passed to Fortran code to describe which method is used.
|
|
92
95
|
tabulated_r_range: numpy.array of shape (tabulation_nr,) and type floating_point_precision
|
|
93
96
|
tabulated_z_range: numpy.array of shape (tabulation_nz,) and type floating_point_precision
|
|
94
97
|
Coordinates of the tabulation points.
|
|
@@ -96,6 +99,7 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
96
99
|
Tabulated Delhommeau integrals.
|
|
97
100
|
"""
|
|
98
101
|
|
|
102
|
+
dispersion_relation_roots = np.empty(1) # dummy array
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
def __init__(self, *,
|
|
@@ -106,6 +110,7 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
106
110
|
tabulation_nb_integration_points=_default_parameters["tabulation_nb_integration_points"],
|
|
107
111
|
tabulation_grid_shape=_default_parameters["tabulation_grid_shape"],
|
|
108
112
|
tabulation_cache_dir=cache_directory(),
|
|
113
|
+
finite_depth_method=_default_parameters["finite_depth_method"],
|
|
109
114
|
finite_depth_prony_decomposition_method=_default_parameters["finite_depth_prony_decomposition_method"],
|
|
110
115
|
floating_point_precision=_default_parameters["floating_point_precision"],
|
|
111
116
|
gf_singularities=_default_parameters["gf_singularities"],
|
|
@@ -121,12 +126,18 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
121
126
|
self.tabulation_grid_shape_index = fortran_enum[tabulation_grid_shape]
|
|
122
127
|
|
|
123
128
|
self.gf_singularities = gf_singularities
|
|
124
|
-
|
|
129
|
+
self.gf_singularities_fortran_enum = {
|
|
125
130
|
'high_freq': self.fortran_core.constants.high_freq,
|
|
126
131
|
'low_freq': self.fortran_core.constants.low_freq,
|
|
127
132
|
'low_freq_with_rankine_part': self.fortran_core.constants.low_freq_with_rankine_part,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
self.finite_depth_method = finite_depth_method
|
|
136
|
+
fortran_enum = {
|
|
137
|
+
'legacy': self.fortran_core.constants.legacy_finite_depth,
|
|
138
|
+
'newer': self.fortran_core.constants.newer_finite_depth,
|
|
128
139
|
}
|
|
129
|
-
self.
|
|
140
|
+
self.finite_depth_method_index = fortran_enum[finite_depth_method]
|
|
130
141
|
|
|
131
142
|
self.floating_point_precision = floating_point_precision
|
|
132
143
|
self.tabulation_nb_integration_points = tabulation_nb_integration_points
|
|
@@ -152,6 +163,7 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
152
163
|
'tabulation_zmin': tabulation_zmin,
|
|
153
164
|
'tabulation_nb_integration_points': tabulation_nb_integration_points,
|
|
154
165
|
'tabulation_grid_shape': tabulation_grid_shape,
|
|
166
|
+
'finite_depth_method': finite_depth_method,
|
|
155
167
|
'finite_depth_prony_decomposition_method': finite_depth_prony_decomposition_method,
|
|
156
168
|
'floating_point_precision': floating_point_precision,
|
|
157
169
|
'gf_singularities': gf_singularities,
|
|
@@ -203,21 +215,25 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
203
215
|
filepath = os.path.join(tabulation_cache_dir, filename)
|
|
204
216
|
|
|
205
217
|
if os.path.exists(filepath):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
try:
|
|
219
|
+
LOG.info("Loading tabulation from %s", filepath)
|
|
220
|
+
loaded_arrays = np.load(filepath)
|
|
221
|
+
self.tabulated_r_range = loaded_arrays["r_range"]
|
|
222
|
+
self.tabulated_z_range = loaded_arrays["z_range"]
|
|
223
|
+
self.tabulated_integrals = loaded_arrays["values"]
|
|
224
|
+
return filename
|
|
225
|
+
except (EOFError, FileNotFoundError, KeyError, ValueError):
|
|
226
|
+
LOG.warning("Error loading tabulation from %s", filepath)
|
|
227
|
+
|
|
228
|
+
self._create_tabulation(tabulation_nr, tabulation_rmax,
|
|
229
|
+
tabulation_nz, tabulation_zmin,
|
|
230
|
+
tabulation_nb_integration_points)
|
|
231
|
+
LOG.debug("Saving tabulation in %s", filepath)
|
|
232
|
+
np.savez_compressed(
|
|
233
|
+
filepath, r_range=self.tabulated_r_range, z_range=self.tabulated_z_range,
|
|
234
|
+
values=self.tabulated_integrals
|
|
235
|
+
)
|
|
236
|
+
return filename
|
|
221
237
|
|
|
222
238
|
def _create_tabulation(self, tabulation_nr, tabulation_rmax,
|
|
223
239
|
tabulation_nz, tabulation_zmin,
|
|
@@ -233,8 +249,14 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
233
249
|
self.tabulated_r_range, self.tabulated_z_range, tabulation_nb_integration_points,
|
|
234
250
|
)
|
|
235
251
|
|
|
252
|
+
@property
|
|
253
|
+
def all_tabulation_parameters(self):
|
|
254
|
+
"""An alias meant to pass to the Fortran functions all the parameters controlling the tabulation in a single item."""
|
|
255
|
+
return (self.tabulation_nb_integration_points, self.tabulation_grid_shape_index,
|
|
256
|
+
self.tabulated_r_range, self.tabulated_z_range, self.tabulated_integrals)
|
|
257
|
+
|
|
236
258
|
@lru_cache(maxsize=128)
|
|
237
|
-
def find_best_exponential_decomposition(self,
|
|
259
|
+
def find_best_exponential_decomposition(self, dimensionless_wavenumber, *, method=None):
|
|
238
260
|
"""Compute the decomposition of a part of the finite water_depth Green function as a sum of exponential functions.
|
|
239
261
|
|
|
240
262
|
Two implementations are available: the legacy Fortran implementation from Nemoh and a newer one written in Python.
|
|
@@ -246,70 +268,134 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
246
268
|
|
|
247
269
|
Parameters
|
|
248
270
|
----------
|
|
249
|
-
dimensionless_omega: float
|
|
250
|
-
dimensionless angular frequency: :math:`kh \\tanh (kh) = \\omega^2 h/g`
|
|
251
271
|
dimensionless_wavenumber: float
|
|
252
272
|
dimensionless wavenumber: :math:`kh`
|
|
253
|
-
method:
|
|
254
|
-
|
|
273
|
+
method: str, optional
|
|
274
|
+
"python" or "fortran". If not provided, uses self.finite_depth_prony_decomposition_method.
|
|
255
275
|
|
|
256
276
|
Returns
|
|
257
277
|
-------
|
|
258
278
|
Tuple[np.ndarray, np.ndarray]
|
|
259
279
|
the amplitude and growth rates of the exponentials
|
|
260
280
|
"""
|
|
281
|
+
kh = dimensionless_wavenumber
|
|
282
|
+
|
|
283
|
+
if method is None:
|
|
284
|
+
method = self.finite_depth_prony_decomposition_method
|
|
261
285
|
|
|
262
286
|
LOG.debug("\tCompute Prony decomposition in finite water_depth Green function "
|
|
263
|
-
"for
|
|
264
|
-
|
|
287
|
+
"for dimensionless_wavenumber=%.2e", dimensionless_wavenumber)
|
|
288
|
+
|
|
289
|
+
if method.lower() == 'python':
|
|
290
|
+
if kh <= 0.1:
|
|
291
|
+
raise NotImplementedError(
|
|
292
|
+
f"{self} cannot evaluate finite depth Green function "
|
|
293
|
+
f"for kh<0.1 (kh={kh})"
|
|
294
|
+
)
|
|
295
|
+
elif kh < 1e5:
|
|
296
|
+
# The function that will be approximated.
|
|
297
|
+
sing_coef = (1 + np.tanh(kh))**2/(1 - np.tanh(kh)**2 + np.tanh(kh)/kh)
|
|
298
|
+
def ref_function(x):
|
|
299
|
+
"""The function that should be approximated by a sum of exponentials."""
|
|
300
|
+
return ((x + kh*np.tanh(kh)) * np.exp(x))/(x*np.sinh(x) - kh*np.tanh(kh)*np.cosh(x)) - sing_coef/(x - kh) - 2
|
|
301
|
+
else:
|
|
302
|
+
# Asymptotic approximation of the function for large kh, including infinite frequency
|
|
303
|
+
def ref_function(x):
|
|
304
|
+
return -2/(1 + np.exp(-2*x)) + 2
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
a, lamda = find_best_exponential_decomposition(ref_function, x_min=-0.1, x_max=20.0, n_exp_range=range(4, 31, 2), tol=1e-4)
|
|
308
|
+
return np.stack([lamda, a])
|
|
309
|
+
except PronyDecompositionFailure as e:
|
|
310
|
+
raise GreenFunctionEvaluationError(
|
|
311
|
+
f"{self} cannot evaluate finite depth Green function "
|
|
312
|
+
f"for kh={dimensionless_wavenumber}"
|
|
313
|
+
) from e
|
|
314
|
+
|
|
315
|
+
elif method.lower() == 'fortran':
|
|
316
|
+
if kh > 1e5:
|
|
317
|
+
raise NotImplementedError("Fortran implementation of the Prony decomposition does not support infinite frequency")
|
|
318
|
+
omega2_h_over_g = kh*np.tanh(kh)
|
|
319
|
+
nexp, pr_d = self.fortran_core.old_prony_decomposition.lisc(omega2_h_over_g, kh)
|
|
320
|
+
return pr_d[0:2, :nexp]
|
|
321
|
+
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError(f"Unrecognized name for the Prony decomposition method: {repr(method)}. Expected 'python' or 'fortran'.")
|
|
324
|
+
|
|
325
|
+
def evaluate_rankine_only(self,
|
|
326
|
+
mesh1, mesh2,
|
|
327
|
+
adjoint_double_layer=True, early_dot_product=True
|
|
328
|
+
):
|
|
329
|
+
r"""Construct the matrices between mesh1 (that can also be a list of points)
|
|
330
|
+
and mesh2 for a Rankine kernel.
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
mesh1: Mesh or CollectionOfMeshes or list of points
|
|
335
|
+
mesh of the receiving body (where the potential is measured)
|
|
336
|
+
if only S is wanted or early_dot_product is False, then only a list
|
|
337
|
+
of points as an array of shape (n, 3) can be passed.
|
|
338
|
+
mesh2: Mesh or CollectionOfMeshes
|
|
339
|
+
mesh of the source body (over which the source distribution is integrated)
|
|
340
|
+
adjoint_double_layer: bool, optional
|
|
341
|
+
compute double layer for direct method (F) or adjoint double layer
|
|
342
|
+
for indirect method (T) matrices (default: True)
|
|
343
|
+
early_dot_product: boolean, optional
|
|
344
|
+
if False, return K as a (n, m, 3) array storing ∫∇G
|
|
345
|
+
if True, return K as a (n, m) array storing ∫∇G·n
|
|
265
346
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
347
|
+
Returns
|
|
348
|
+
-------
|
|
349
|
+
tuple of real-valued numpy arrays
|
|
350
|
+
the matrices :math:`S` and :math:`K`
|
|
351
|
+
"""
|
|
352
|
+
collocation_points, early_dot_product_normals = \
|
|
353
|
+
self._get_colocation_points_and_normals(mesh1, mesh2, adjoint_double_layer)
|
|
271
354
|
|
|
272
|
-
|
|
273
|
-
|
|
355
|
+
S, K = self._init_matrices(
|
|
356
|
+
(collocation_points.shape[0], mesh2.nb_faces), early_dot_product
|
|
357
|
+
)
|
|
274
358
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
359
|
+
self.fortran_core.matrices.add_rankine_term_only(
|
|
360
|
+
collocation_points, early_dot_product_normals,
|
|
361
|
+
mesh2.vertices, mesh2.faces + 1,
|
|
362
|
+
mesh2.faces_centers, mesh2.faces_normals,
|
|
363
|
+
mesh2.faces_areas, mesh2.faces_radiuses,
|
|
364
|
+
*mesh2.quadrature_points,
|
|
365
|
+
adjoint_double_layer,
|
|
366
|
+
S, K)
|
|
278
367
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
368
|
+
if mesh1 is mesh2:
|
|
369
|
+
self.fortran_core.matrices.add_diagonal_term(
|
|
370
|
+
mesh2.faces_centers, early_dot_product_normals, np.inf, K,
|
|
371
|
+
)
|
|
283
372
|
|
|
284
|
-
|
|
285
|
-
LOG.warning("No suitable exponential decomposition has been found"
|
|
286
|
-
"for dimless_omega=%.2e and dimless_wavenumber=%.2e",
|
|
287
|
-
dimensionless_omega, dimensionless_wavenumber)
|
|
373
|
+
S, K = np.real(S), np.real(K)
|
|
288
374
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
375
|
+
if np.any(np.isnan(S)) or np.any(np.isnan(K)):
|
|
376
|
+
raise GreenFunctionEvaluationError(
|
|
377
|
+
"Green function returned a NaN in the interaction matrix.\n"
|
|
378
|
+
"It could be due to overlapping panels.")
|
|
293
379
|
|
|
294
|
-
|
|
295
|
-
|
|
380
|
+
if early_dot_product:
|
|
381
|
+
K = K.reshape((collocation_points.shape[0], mesh2.nb_faces))
|
|
296
382
|
|
|
297
|
-
|
|
298
|
-
# It is not clear where it comes from exactly in the theory...
|
|
299
|
-
a = np.concatenate([a, np.array([2])])
|
|
300
|
-
lamda = np.concatenate([lamda, np.array([0.0])])
|
|
383
|
+
return S, K
|
|
301
384
|
|
|
302
|
-
return a, lamda
|
|
303
385
|
|
|
304
|
-
def evaluate(self,
|
|
386
|
+
def evaluate(self,
|
|
387
|
+
mesh1, mesh2,
|
|
388
|
+
free_surface=0.0, water_depth=np.inf, wavenumber=1.0,
|
|
389
|
+
adjoint_double_layer=True, early_dot_product=True
|
|
390
|
+
):
|
|
305
391
|
r"""The main method of the class, called by the engine to assemble the influence matrices.
|
|
306
392
|
|
|
307
393
|
Parameters
|
|
308
394
|
----------
|
|
309
|
-
mesh1:
|
|
395
|
+
mesh1: MeshLike or list of points
|
|
310
396
|
mesh of the receiving body (where the potential is measured)
|
|
311
397
|
if only S is wanted or early_dot_product is False, then only a list of points as an array of shape (n, 3) can be passed.
|
|
312
|
-
mesh2:
|
|
398
|
+
mesh2: MeshLike
|
|
313
399
|
mesh of the source body (over which the source distribution is integrated)
|
|
314
400
|
free_surface: float, optional
|
|
315
401
|
position of the free surface (default: :math:`z = 0`)
|
|
@@ -327,77 +413,61 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
327
413
|
-------
|
|
328
414
|
tuple of numpy arrays
|
|
329
415
|
the matrices :math:`S` and :math:`K`
|
|
416
|
+
the dtype of the matrix can be real or complex and depends on self.floating_point_precision
|
|
330
417
|
"""
|
|
331
418
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
419
|
+
if free_surface == np.inf: # No free surface, only a single Rankine source term
|
|
420
|
+
if water_depth != np.inf:
|
|
421
|
+
raise ValueError("When setting free_surface=inf, "
|
|
422
|
+
"the water depth should also be infinite "
|
|
423
|
+
f"(got water_depth={water_depth})")
|
|
335
424
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
425
|
+
return self.evaluate_rankine_only(
|
|
426
|
+
mesh1, mesh2,
|
|
427
|
+
adjoint_double_layer=adjoint_double_layer,
|
|
428
|
+
early_dot_product=early_dot_product,
|
|
429
|
+
)
|
|
339
430
|
|
|
340
|
-
|
|
431
|
+
# Main case:
|
|
432
|
+
collocation_points, early_dot_product_normals = \
|
|
433
|
+
self._get_colocation_points_and_normals(mesh1, mesh2, adjoint_double_layer)
|
|
341
434
|
|
|
342
|
-
|
|
435
|
+
S, K = self._init_matrices(
|
|
436
|
+
(collocation_points.shape[0], mesh2.nb_faces), early_dot_product
|
|
437
|
+
)
|
|
343
438
|
|
|
344
|
-
|
|
345
|
-
coeffs = np.array((1.0, 1.0, 0.0))
|
|
346
|
-
elif wavenumber == np.inf:
|
|
347
|
-
coeffs = np.array((1.0, -1.0, 0.0))
|
|
348
|
-
else:
|
|
349
|
-
if self.gf_singularities == "high_freq":
|
|
350
|
-
coeffs = np.array((1.0, -1.0, 1.0))
|
|
351
|
-
else: # low_freq or low_freq_with_rankine_part
|
|
352
|
-
coeffs = np.array((1.0, 1.0, 1.0))
|
|
353
|
-
|
|
354
|
-
else: # Finite water_depth
|
|
355
|
-
if wavenumber == 0.0 or wavenumber == np.inf:
|
|
356
|
-
raise NotImplementedError("Zero or infinite frequencies not implemented for finite depth.")
|
|
357
|
-
else:
|
|
358
|
-
a_exp, lamda_exp = self.find_best_exponential_decomposition(
|
|
359
|
-
wavenumber*water_depth*np.tanh(wavenumber*water_depth),
|
|
360
|
-
wavenumber*water_depth,
|
|
361
|
-
)
|
|
362
|
-
coeffs = np.array((1.0, 1.0, 1.0))
|
|
363
|
-
|
|
364
|
-
if isinstance(mesh1, Mesh) or isinstance(mesh1, CollectionOfMeshes):
|
|
365
|
-
collocation_points = mesh1.faces_centers
|
|
366
|
-
nb_collocation_points = mesh1.nb_faces
|
|
367
|
-
if not adjoint_double_layer: # Computing the D matrix
|
|
368
|
-
early_dot_product_normals = mesh2.faces_normals
|
|
369
|
-
else: # Computing the K matrix
|
|
370
|
-
early_dot_product_normals = mesh1.faces_normals
|
|
371
|
-
|
|
372
|
-
elif isinstance(mesh1, np.ndarray) and mesh1.ndim == 2 and mesh1.shape[1] == 3:
|
|
373
|
-
# This is used when computing potential or velocity at given points in postprocessing
|
|
374
|
-
collocation_points = mesh1
|
|
375
|
-
nb_collocation_points = mesh1.shape[0]
|
|
376
|
-
if not adjoint_double_layer: # Computing the D matrix
|
|
377
|
-
early_dot_product_normals = mesh2.faces_normals
|
|
378
|
-
else: # Computing the K matrix
|
|
379
|
-
early_dot_product_normals = np.zeros((nb_collocation_points, 3))
|
|
380
|
-
# Dummy argument since this method is meant to be used either
|
|
381
|
-
# - to compute potential, then only S is needed and early_dot_product_normals is irrelevant,
|
|
382
|
-
# - to compute velocity, then the adjoint full gradient is needed and early_dot_product is False and this value is unused.
|
|
383
|
-
# TODO: add an only_S argument and return an error here if (early_dot_product and not only_S)
|
|
439
|
+
wavenumber = float(wavenumber)
|
|
384
440
|
|
|
441
|
+
# Overrides gf_singularities setting in some specific cases, else use the class one.
|
|
442
|
+
if water_depth < np.inf and self.finite_depth_method == 'legacy' and not self.gf_singularities == 'low_freq':
|
|
443
|
+
gf_singularities = "low_freq" # Reproduce legacy method behavior
|
|
444
|
+
LOG.debug(
|
|
445
|
+
f"Overriding gf_singularities='{self.gf_singularities}' because of finite_depth_method=='legacy'"
|
|
446
|
+
)
|
|
447
|
+
elif wavenumber == 0.0 and not self.gf_singularities == 'low_freq':
|
|
448
|
+
gf_singularities = "low_freq"
|
|
449
|
+
LOG.debug(
|
|
450
|
+
f"Overriding gf_singularities='{self.gf_singularities}' because of wavenumber==0.0"
|
|
451
|
+
)
|
|
452
|
+
elif wavenumber == np.inf and not self.gf_singularities == 'high_freq':
|
|
453
|
+
gf_singularities = "high_freq"
|
|
454
|
+
LOG.debug(
|
|
455
|
+
f"Overriding gf_singularities='{self.gf_singularities}' because of wavenumber==np.inf"
|
|
456
|
+
)
|
|
457
|
+
elif np.any(abs(mesh2.faces_centers[:, 2]) < 1e-6) and not self.gf_singularities == 'low_freq':
|
|
458
|
+
gf_singularities = "low_freq"
|
|
459
|
+
LOG.warning(
|
|
460
|
+
f"Overriding gf_singularities='{self.gf_singularities}' because of free surface panels, "
|
|
461
|
+
"which are currently only supported by gf_singularities='low_freq'"
|
|
462
|
+
)
|
|
385
463
|
else:
|
|
386
|
-
|
|
464
|
+
gf_singularities = self.gf_singularities
|
|
465
|
+
gf_singularities_index = self.gf_singularities_fortran_enum[gf_singularities]
|
|
387
466
|
|
|
388
|
-
if
|
|
389
|
-
|
|
390
|
-
raise NotImplementedError("Free surface panels are only supported for cpt.Delhommeau(..., gf_singularities='low_freq').")
|
|
391
|
-
|
|
392
|
-
if self.floating_point_precision == "float32":
|
|
393
|
-
dtype = "complex64"
|
|
394
|
-
elif self.floating_point_precision == "float64":
|
|
395
|
-
dtype = "complex128"
|
|
467
|
+
if water_depth == np.inf:
|
|
468
|
+
prony_decomposition = np.zeros((1, 1)) # Dummy array that won't actually be used by the fortran code.
|
|
396
469
|
else:
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
S = np.empty((nb_collocation_points, mesh2.nb_faces), order="F", dtype=dtype)
|
|
400
|
-
K = np.empty((nb_collocation_points, mesh2.nb_faces, 1 if early_dot_product else 3), order="F", dtype=dtype)
|
|
470
|
+
prony_decomposition = self.find_best_exponential_decomposition(wavenumber*water_depth)
|
|
401
471
|
|
|
402
472
|
# Main call to Fortran code
|
|
403
473
|
self.fortran_core.matrices.build_matrices(
|
|
@@ -407,19 +477,24 @@ class Delhommeau(AbstractGreenFunction):
|
|
|
407
477
|
mesh2.faces_areas, mesh2.faces_radiuses,
|
|
408
478
|
*mesh2.quadrature_points,
|
|
409
479
|
wavenumber, water_depth,
|
|
410
|
-
|
|
411
|
-
self.
|
|
412
|
-
|
|
413
|
-
lamda_exp, a_exp,
|
|
414
|
-
mesh1 is mesh2, self.gf_singularities_index, adjoint_double_layer,
|
|
480
|
+
*self.all_tabulation_parameters,
|
|
481
|
+
self.finite_depth_method_index, prony_decomposition, self.dispersion_relation_roots,
|
|
482
|
+
gf_singularities_index, adjoint_double_layer,
|
|
415
483
|
S, K
|
|
416
484
|
)
|
|
417
485
|
|
|
486
|
+
if mesh1 is mesh2:
|
|
487
|
+
self.fortran_core.matrices.add_diagonal_term(
|
|
488
|
+
mesh2.faces_centers, early_dot_product_normals, free_surface, K,
|
|
489
|
+
)
|
|
490
|
+
|
|
418
491
|
if np.any(np.isnan(S)) or np.any(np.isnan(K)):
|
|
419
|
-
raise
|
|
492
|
+
raise GreenFunctionEvaluationError(
|
|
493
|
+
"Green function returned a NaN in the interaction matrix.\n"
|
|
420
494
|
"It could be due to overlapping panels.")
|
|
421
495
|
|
|
422
|
-
if early_dot_product:
|
|
496
|
+
if early_dot_product:
|
|
497
|
+
K = K.reshape((collocation_points.shape[0], mesh2.nb_faces))
|
|
423
498
|
|
|
424
499
|
return S, K
|
|
425
500
|
|