capytaine 2.2.1__cp38-cp38-macosx_14_0_arm64.whl → 2.3.1__cp38-cp38-macosx_14_0_arm64.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.
Files changed (48) hide show
  1. capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. capytaine/.dylibs/libgfortran.5.dylib +0 -0
  3. capytaine/.dylibs/libquadmath.0.dylib +0 -0
  4. capytaine/__about__.py +1 -1
  5. capytaine/__init__.py +2 -1
  6. capytaine/bem/airy_waves.py +7 -2
  7. capytaine/bem/problems_and_results.py +91 -39
  8. capytaine/bem/solver.py +128 -40
  9. capytaine/bodies/bodies.py +46 -18
  10. capytaine/bodies/predefined/rectangles.py +2 -0
  11. capytaine/green_functions/FinGreen3D/.gitignore +1 -0
  12. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
  13. capytaine/green_functions/FinGreen3D/LICENSE +165 -0
  14. capytaine/green_functions/FinGreen3D/Makefile +16 -0
  15. capytaine/green_functions/FinGreen3D/README.md +24 -0
  16. capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
  17. capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
  18. capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
  19. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
  20. capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
  21. capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
  22. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
  23. capytaine/green_functions/abstract_green_function.py +55 -3
  24. capytaine/green_functions/delhommeau.py +205 -130
  25. capytaine/green_functions/hams.py +204 -0
  26. capytaine/green_functions/libs/Delhommeau_float32.cpython-38-darwin.so +0 -0
  27. capytaine/green_functions/libs/Delhommeau_float64.cpython-38-darwin.so +0 -0
  28. capytaine/io/bemio.py +14 -2
  29. capytaine/io/mesh_loaders.py +1 -1
  30. capytaine/io/wamit.py +479 -0
  31. capytaine/io/xarray.py +261 -117
  32. capytaine/matrices/linear_solvers.py +1 -1
  33. capytaine/meshes/clipper.py +1 -0
  34. capytaine/meshes/collections.py +19 -1
  35. capytaine/meshes/mesh_like_protocol.py +37 -0
  36. capytaine/meshes/meshes.py +28 -8
  37. capytaine/meshes/symmetric.py +89 -10
  38. capytaine/post_pro/kochin.py +4 -4
  39. capytaine/tools/lists_of_points.py +3 -3
  40. capytaine/tools/prony_decomposition.py +60 -4
  41. capytaine/tools/symbolic_multiplication.py +30 -4
  42. capytaine/tools/timer.py +66 -0
  43. {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/METADATA +6 -10
  44. capytaine-2.3.1.dist-info/RECORD +92 -0
  45. capytaine-2.2.1.dist-info/RECORD +0 -76
  46. {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/LICENSE +0 -0
  47. {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/WHEEL +0 -0
  48. {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,2 @@
1
+ # Green-function-in-deep-water
2
+ Fortran routine for the evaluation of the Green function in deep water
@@ -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-2019 Matthieu Ancellin
3
- # See LICENSE file at <https://github.com/mancellin/capytaine>
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, sea_bottom, wavenumber):
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.meshes.meshes import Mesh
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
- finite_depth_prony_decomposition_method="fortran",
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
- Integer passed to Fortran code to describe which method is used.
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
- fortran_enum = {
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.gf_singularities_index = fortran_enum[gf_singularities]
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
- LOG.info("Loading tabulation from %s", filepath)
207
- loaded_arrays = np.load(filepath)
208
- self.tabulated_r_range = loaded_arrays["r_range"]
209
- self.tabulated_z_range = loaded_arrays["z_range"]
210
- self.tabulated_integrals = loaded_arrays["values"]
211
-
212
- else:
213
- self._create_tabulation(tabulation_nr, tabulation_rmax,
214
- tabulation_nz, tabulation_zmin,
215
- tabulation_nb_integration_points)
216
- LOG.debug("Saving tabulation in %s", filepath)
217
- np.savez_compressed(
218
- filepath, r_range=self.tabulated_r_range, z_range=self.tabulated_z_range,
219
- values=self.tabulated_integrals
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, dimensionless_omega, dimensionless_wavenumber):
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: string, optional
254
- the implementation that should be used to compute the Prony decomposition
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 dimless_omega=%.2e and dimless_wavenumber=%.2e",
264
- dimensionless_omega, dimensionless_wavenumber)
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
- if self.finite_depth_prony_decomposition_method.lower() == 'python':
267
- # The function that will be approximated.
268
- @np.vectorize
269
- def f(x):
270
- return self.fortran_core.initialize_green_wave.ff(x, dimensionless_omega, dimensionless_wavenumber)
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
- # Try different increasing number of exponentials
273
- for n_exp in range(4, 31, 2):
355
+ S, K = self._init_matrices(
356
+ (collocation_points.shape[0], mesh2.nb_faces), early_dot_product
357
+ )
274
358
 
275
- # The coefficients are computed on a resolution of 4*n_exp+1 ...
276
- X = np.linspace(-0.1, 20.0, 4*n_exp+1)
277
- a, lamda = exponential_decomposition(X, f(X), n_exp)
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
- # ... and they are evaluated on a finer discretization.
280
- X = np.linspace(-0.1, 20.0, 8*n_exp+1)
281
- if error_exponential_decomposition(X, f(X), a, lamda) < 1e-4:
282
- break
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
- else:
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
- elif self.finite_depth_prony_decomposition_method.lower() == 'fortran':
290
- lamda, a, nexp = self.fortran_core.old_prony_decomposition.lisc(dimensionless_omega, dimensionless_wavenumber)
291
- lamda = lamda[:nexp]
292
- a = a[:nexp]
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
- else:
295
- raise ValueError("Unrecognized method name for the Prony decomposition.")
380
+ if early_dot_product:
381
+ K = K.reshape((collocation_points.shape[0], mesh2.nb_faces))
296
382
 
297
- # Add one more exponential function (actually a constant).
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, mesh1, mesh2, free_surface=0.0, water_depth=np.inf, wavenumber=1.0, adjoint_double_layer=True, early_dot_product=True):
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: Mesh or CollectionOfMeshes or list of points
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: Mesh or CollectionOfMeshes
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
- wavenumber = float(wavenumber)
333
-
334
- if free_surface == np.inf: # No free surface, only a single Rankine source term
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
- a_exp, lamda_exp = np.empty(1), np.empty(1) # Dummy arrays that won't actually be used by the fortran code.
337
-
338
- coeffs = np.array((1.0, 0.0, 0.0))
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
- elif water_depth == np.inf:
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
- a_exp, lamda_exp = np.empty(1), np.empty(1) # Idem
435
+ S, K = self._init_matrices(
436
+ (collocation_points.shape[0], mesh2.nb_faces), early_dot_product
437
+ )
343
438
 
344
- if wavenumber == 0.0:
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
- raise ValueError(f"Unrecognized first input for {self.__class__.__name__}.evaluate:\n{mesh1}")
464
+ gf_singularities = self.gf_singularities
465
+ gf_singularities_index = self.gf_singularities_fortran_enum[gf_singularities]
387
466
 
388
- if (np.any(abs(mesh2.faces_centers[:, 2]) < 1e-6) # free surface panel
389
- and self.gf_singularities != "low_freq"):
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
- raise NotImplementedError(f"Unsupported floating point precision: {self.floating_point_precision}")
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
- coeffs,
411
- self.tabulation_nb_integration_points, self.tabulation_grid_shape_index,
412
- self.tabulated_r_range, self.tabulated_z_range, self.tabulated_integrals,
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 RuntimeError("Green function returned a NaN in the interaction matrix.\n"
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: K = K.reshape((nb_collocation_points, mesh2.nb_faces))
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