capytaine 2.2.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 (76) 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 +16 -0
  5. capytaine/__init__.py +35 -0
  6. capytaine/bem/__init__.py +0 -0
  7. capytaine/bem/airy_waves.py +106 -0
  8. capytaine/bem/engines.py +441 -0
  9. capytaine/bem/problems_and_results.py +548 -0
  10. capytaine/bem/solver.py +506 -0
  11. capytaine/bodies/__init__.py +4 -0
  12. capytaine/bodies/bodies.py +1193 -0
  13. capytaine/bodies/dofs.py +19 -0
  14. capytaine/bodies/predefined/__init__.py +6 -0
  15. capytaine/bodies/predefined/cylinders.py +151 -0
  16. capytaine/bodies/predefined/rectangles.py +109 -0
  17. capytaine/bodies/predefined/spheres.py +70 -0
  18. capytaine/green_functions/__init__.py +2 -0
  19. capytaine/green_functions/abstract_green_function.py +12 -0
  20. capytaine/green_functions/delhommeau.py +432 -0
  21. capytaine/green_functions/libs/Delhommeau_float32.cpython-38-darwin.so +0 -0
  22. capytaine/green_functions/libs/Delhommeau_float64.cpython-38-darwin.so +0 -0
  23. capytaine/green_functions/libs/__init__.py +0 -0
  24. capytaine/io/__init__.py +0 -0
  25. capytaine/io/bemio.py +141 -0
  26. capytaine/io/legacy.py +328 -0
  27. capytaine/io/mesh_loaders.py +1086 -0
  28. capytaine/io/mesh_writers.py +692 -0
  29. capytaine/io/meshio.py +38 -0
  30. capytaine/io/xarray.py +524 -0
  31. capytaine/matrices/__init__.py +16 -0
  32. capytaine/matrices/block.py +592 -0
  33. capytaine/matrices/block_toeplitz.py +325 -0
  34. capytaine/matrices/builders.py +89 -0
  35. capytaine/matrices/linear_solvers.py +232 -0
  36. capytaine/matrices/low_rank.py +395 -0
  37. capytaine/meshes/__init__.py +6 -0
  38. capytaine/meshes/clipper.py +464 -0
  39. capytaine/meshes/collections.py +324 -0
  40. capytaine/meshes/geometry.py +409 -0
  41. capytaine/meshes/meshes.py +870 -0
  42. capytaine/meshes/predefined/__init__.py +6 -0
  43. capytaine/meshes/predefined/cylinders.py +314 -0
  44. capytaine/meshes/predefined/rectangles.py +261 -0
  45. capytaine/meshes/predefined/spheres.py +62 -0
  46. capytaine/meshes/properties.py +276 -0
  47. capytaine/meshes/quadratures.py +80 -0
  48. capytaine/meshes/quality.py +448 -0
  49. capytaine/meshes/surface_integrals.py +63 -0
  50. capytaine/meshes/symmetric.py +383 -0
  51. capytaine/post_pro/__init__.py +6 -0
  52. capytaine/post_pro/free_surfaces.py +88 -0
  53. capytaine/post_pro/impedance.py +92 -0
  54. capytaine/post_pro/kochin.py +54 -0
  55. capytaine/post_pro/rao.py +60 -0
  56. capytaine/tools/__init__.py +0 -0
  57. capytaine/tools/cache_on_disk.py +26 -0
  58. capytaine/tools/deprecation_handling.py +18 -0
  59. capytaine/tools/lists_of_points.py +52 -0
  60. capytaine/tools/lru_cache.py +49 -0
  61. capytaine/tools/optional_imports.py +27 -0
  62. capytaine/tools/prony_decomposition.py +94 -0
  63. capytaine/tools/symbolic_multiplication.py +123 -0
  64. capytaine/ui/__init__.py +0 -0
  65. capytaine/ui/cli.py +28 -0
  66. capytaine/ui/rich.py +5 -0
  67. capytaine/ui/vtk/__init__.py +3 -0
  68. capytaine/ui/vtk/animation.py +329 -0
  69. capytaine/ui/vtk/body_viewer.py +28 -0
  70. capytaine/ui/vtk/helpers.py +82 -0
  71. capytaine/ui/vtk/mesh_viewer.py +461 -0
  72. capytaine-2.2.1.dist-info/LICENSE +674 -0
  73. capytaine-2.2.1.dist-info/METADATA +754 -0
  74. capytaine-2.2.1.dist-info/RECORD +76 -0
  75. capytaine-2.2.1.dist-info/WHEEL +4 -0
  76. capytaine-2.2.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,506 @@
1
+ # Copyright (C) 2017-2024 Matthieu Ancellin
2
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
3
+ """Solver for the BEM problem.
4
+
5
+ .. code-block:: python
6
+
7
+ problem = RadiationProblem(...)
8
+ result = BEMSolver(green_functions=..., engine=...).solve(problem)
9
+
10
+ """
11
+
12
+ import logging
13
+
14
+ import numpy as np
15
+
16
+ from datetime import datetime
17
+
18
+ from rich.progress import track
19
+
20
+ from capytaine.bem.problems_and_results import LinearPotentialFlowProblem
21
+ from capytaine.green_functions.delhommeau import Delhommeau
22
+ from capytaine.bem.engines import BasicMatrixEngine
23
+ from capytaine.io.xarray import problems_from_dataset, assemble_dataset, kochin_data_array
24
+ from capytaine.tools.optional_imports import silently_import_optional_dependency
25
+ from capytaine.tools.lists_of_points import _normalize_points, _normalize_free_surface_points
26
+ from capytaine.tools.symbolic_multiplication import supporting_symbolic_multiplication
27
+
28
+ LOG = logging.getLogger(__name__)
29
+
30
+ class BEMSolver:
31
+ """
32
+ Solver for linear potential flow problems.
33
+
34
+ Parameters
35
+ ----------
36
+ green_function: AbstractGreenFunction, optional
37
+ Object handling the computation of the Green function.
38
+ (default: :class:`~capytaine.green_function.delhommeau.Delhommeau`)
39
+ engine: MatrixEngine, optional
40
+ Object handling the building of matrices and the resolution of linear systems with these matrices.
41
+ (default: :class:`~capytaine.bem.engines.BasicMatrixEngine`)
42
+
43
+ Attributes
44
+ ----------
45
+ exportable_settings : dict
46
+ Settings of the solver that can be saved to reinit the same solver later.
47
+ """
48
+
49
+ def __init__(self, *, green_function=None, engine=None):
50
+ self.green_function = Delhommeau() if green_function is None else green_function
51
+ self.engine = BasicMatrixEngine() if engine is None else engine
52
+
53
+ try:
54
+ self.exportable_settings = {
55
+ **self.green_function.exportable_settings,
56
+ **self.engine.exportable_settings
57
+ }
58
+ except AttributeError:
59
+ pass
60
+
61
+ def __str__(self):
62
+ return f"BEMSolver(engine={self.engine}, green_function={self.green_function})"
63
+
64
+ def __repr__(self):
65
+ return self.__str__()
66
+
67
+ def _repr_pretty_(self, p, cycle):
68
+ p.text(self.__str__())
69
+
70
+ @classmethod
71
+ def from_exported_settings(settings):
72
+ raise NotImplementedError
73
+
74
+ def solve(self, problem, method='indirect', keep_details=True, _check_wavelength=True):
75
+ """Solve the linear potential flow problem.
76
+
77
+ Parameters
78
+ ----------
79
+ problem: LinearPotentialFlowProblem
80
+ the problem to be solved
81
+ method: string, optional
82
+ select boundary integral approach indirect (i.e.Nemoh)/direct (i.e.WAMIT) (default: indirect)
83
+ keep_details: bool, optional
84
+ if True, store the sources and the potential on the floating body in the output object
85
+ (default: True)
86
+ _check_wavelength: bool, optional (default: True)
87
+ If True, the frequencies are compared to the mesh resolution and
88
+ the estimated first irregular frequency to warn the user.
89
+
90
+ Returns
91
+ -------
92
+ LinearPotentialFlowResult
93
+ an object storing the problem data and its results
94
+ """
95
+ LOG.info("Solve %s.", problem)
96
+
97
+ if _check_wavelength:
98
+ self._check_wavelength_and_mesh_resolution([problem])
99
+ self._check_wavelength_and_irregular_frequencies([problem])
100
+
101
+ if problem.forward_speed != 0.0:
102
+ omega, wavenumber = problem.encounter_omega, problem.encounter_wavenumber
103
+ else:
104
+ omega, wavenumber = problem.omega, problem.wavenumber
105
+
106
+ linear_solver = supporting_symbolic_multiplication(self.engine.linear_solver)
107
+ if (method == 'direct'):
108
+ if problem.forward_speed != 0.0:
109
+ raise NotImplementedError("Direct solver is not able to solve problems with forward speed.")
110
+
111
+ S, D = self.engine.build_matrices(
112
+ problem.body.mesh_including_lid, problem.body.mesh_including_lid,
113
+ problem.free_surface, problem.water_depth, wavenumber,
114
+ self.green_function, adjoint_double_layer=False
115
+ )
116
+
117
+ potential = linear_solver(D, S @ problem.boundary_condition)
118
+ pressure = 1j * omega * problem.rho * potential
119
+ sources = None
120
+ else:
121
+ S, K = self.engine.build_matrices(
122
+ problem.body.mesh_including_lid, problem.body.mesh_including_lid,
123
+ problem.free_surface, problem.water_depth, wavenumber,
124
+ self.green_function, adjoint_double_layer=True
125
+ )
126
+
127
+ sources = linear_solver(K, problem.boundary_condition)
128
+ potential = S @ sources
129
+ pressure = 1j * omega * problem.rho * potential
130
+ if problem.forward_speed != 0.0:
131
+ result = problem.make_results_container(sources=sources)
132
+ # Temporary result object to compute the ∇Φ term
133
+ nabla_phi = self._compute_potential_gradient(problem.body.mesh_including_lid, result)
134
+ pressure += problem.rho * problem.forward_speed * nabla_phi[:, 0]
135
+
136
+ pressure_on_hull = pressure[:problem.body.mesh.nb_faces] # Discards pressure on lid if any
137
+ forces = problem.body.integrate_pressure(pressure_on_hull)
138
+
139
+ if not keep_details:
140
+ result = problem.make_results_container(forces)
141
+ else:
142
+ result = problem.make_results_container(forces, sources, potential, pressure)
143
+
144
+ LOG.debug("Done!")
145
+
146
+ return result
147
+
148
+ def solve_all(self, problems, *, method='indirect', n_jobs=1, progress_bar=True, _check_wavelength=True, **kwargs):
149
+ """Solve several problems.
150
+ Optional keyword arguments are passed to `BEMSolver.solve`.
151
+
152
+ Parameters
153
+ ----------
154
+ problems: list of LinearPotentialFlowProblem
155
+ several problems to be solved
156
+ method: string, optional
157
+ select boundary integral approach indirect (i.e.Nemoh)/direct (i.e.WAMIT) (default: indirect)
158
+ n_jobs: int, optional (default: 1)
159
+ the number of jobs to run in parallel using the optional dependency `joblib`
160
+ By defaults: do not use joblib and solve sequentially.
161
+ progress_bar: bool, optional (default: True)
162
+ Display a progress bar while solving
163
+ _check_wavelength: bool, optional (default: True)
164
+ If True, the frequencies are compared to the mesh resolution and
165
+ the estimated first irregular frequency to warn the user.
166
+
167
+ Returns
168
+ -------
169
+ list of LinearPotentialFlowResult
170
+ the solved problems
171
+ """
172
+ if _check_wavelength:
173
+ self._check_wavelength_and_mesh_resolution(problems)
174
+ self._check_wavelength_and_irregular_frequencies(problems)
175
+
176
+ if n_jobs == 1: # force sequential resolution
177
+ problems = sorted(problems)
178
+ if progress_bar:
179
+ problems = track(problems, total=len(problems), description="Solving BEM problems")
180
+ return [self.solve(pb, method=method, _check_wavelength=False, **kwargs) for pb in problems]
181
+ else:
182
+ joblib = silently_import_optional_dependency("joblib")
183
+ if joblib is None:
184
+ raise ImportError(f"Setting the `n_jobs` argument to {n_jobs} requires the missing optional dependency 'joblib'.")
185
+ groups_of_problems = LinearPotentialFlowProblem._group_for_parallel_resolution(problems)
186
+ parallel = joblib.Parallel(return_as="generator", n_jobs=n_jobs)
187
+ groups_of_results = parallel(joblib.delayed(self.solve_all)(grp, method=method, n_jobs=1, progress_bar=False, _check_wavelength=False, **kwargs) for grp in groups_of_problems)
188
+ if progress_bar:
189
+ groups_of_results = track(groups_of_results,
190
+ total=len(groups_of_problems),
191
+ description=f"Solving BEM problems with {n_jobs} threads:")
192
+ results = [res for grp in groups_of_results for res in grp] # flatten the nested list
193
+ return results
194
+
195
+ @staticmethod
196
+ def _check_wavelength_and_mesh_resolution(problems):
197
+ """Display a warning if some of the problems have a mesh resolution
198
+ that might not be sufficient for the given wavelength."""
199
+ LOG.debug("Check wavelength with mesh resolution.")
200
+ risky_problems = [pb for pb in problems
201
+ if 0.0 < pb.wavelength < pb.body.minimal_computable_wavelength]
202
+ nb_risky_problems = len(risky_problems)
203
+ if nb_risky_problems == 1:
204
+ pb = risky_problems[0]
205
+ freq_type = risky_problems[0].provided_freq_type
206
+ freq = pb.__getattribute__(freq_type)
207
+ LOG.warning(f"Mesh resolution for {pb}:\n"
208
+ f"The resolution of the mesh of the body {pb.body.__short_str__()} might "
209
+ f"be insufficient for {freq_type}={freq}.\n"
210
+ "This warning appears because the largest panel of this mesh "
211
+ f"has radius {pb.body.mesh.faces_radiuses.max():.3f} > wavelength/8."
212
+ )
213
+ elif nb_risky_problems > 1:
214
+ freq_type = risky_problems[0].provided_freq_type
215
+ freqs = np.array([float(pb.__getattribute__(freq_type)) for pb in risky_problems])
216
+ LOG.warning(f"Mesh resolution for {nb_risky_problems} problems:\n"
217
+ "The resolution of the mesh might be insufficient "
218
+ f"for {freq_type} ranging from {freqs.min():.3f} to {freqs.max():.3f}.\n"
219
+ "This warning appears when the largest panel of this mesh "
220
+ "has radius > wavelength/8."
221
+ )
222
+
223
+ @staticmethod
224
+ def _check_wavelength_and_irregular_frequencies(problems):
225
+ """Display a warning if some of the problems might encounter irregular frequencies."""
226
+ LOG.debug("Check wavelength with estimated irregular frequency.")
227
+ risky_problems = [pb for pb in problems
228
+ if pb.body.first_irregular_frequency_estimate(g=pb.g) < pb.omega < np.inf]
229
+ nb_risky_problems = len(risky_problems)
230
+ if nb_risky_problems >= 1:
231
+ if any(pb.body.lid_mesh is None for pb in problems):
232
+ recommendation = "Setting a lid for the floating body is recommended."
233
+ else:
234
+ recommendation = "The lid might need to be closer to the free surface."
235
+ if nb_risky_problems == 1:
236
+ pb = risky_problems[0]
237
+ freq_type = risky_problems[0].provided_freq_type
238
+ freq = pb.__getattribute__(freq_type)
239
+ LOG.warning(f"Irregular frequencies for {pb}:\n"
240
+ f"The body {pb.body.__short_str__()} might display irregular frequencies "
241
+ f"for {freq_type}={freq}.\n"
242
+ + recommendation
243
+ )
244
+ elif nb_risky_problems > 1:
245
+ freq_type = risky_problems[0].provided_freq_type
246
+ freqs = np.array([float(pb.__getattribute__(freq_type)) for pb in risky_problems])
247
+ LOG.warning(f"Irregular frequencies for {nb_risky_problems} problems:\n"
248
+ "Irregular frequencies might be encountered "
249
+ f"for {freq_type} ranging from {freqs.min():.3f} to {freqs.max():.3f}.\n"
250
+ + recommendation
251
+ )
252
+
253
+ def fill_dataset(self, dataset, bodies, *, method='indirect', n_jobs=1, _check_wavelength=True, **kwargs):
254
+ """Solve a set of problems defined by the coordinates of an xarray dataset.
255
+
256
+ Parameters
257
+ ----------
258
+ dataset : xarray Dataset
259
+ dataset containing the problems parameters: frequency, radiating_dof, water_depth, ...
260
+ bodies : FloatingBody or list of FloatingBody
261
+ The body or bodies involved in the problems
262
+ They should all have different names.
263
+ method: string, optional
264
+ select boundary integral approach indirect (i.e.Nemoh)/direct (i.e.WAMIT) (default: indirect)
265
+ n_jobs: int, optional (default: 1)
266
+ the number of jobs to run in parallel using the optional dependency `joblib`
267
+ By defaults: do not use joblib and solve sequentially.
268
+ progress_bar: bool, optional (default: True)
269
+ Display a progress bar while solving
270
+ _check_wavelength: bool, optional (default: True)
271
+ If True, the frequencies are compared to the mesh resolution and
272
+ the estimated first irregular frequency to warn the user.
273
+
274
+ Returns
275
+ -------
276
+ xarray Dataset
277
+ """
278
+ attrs = {'start_of_computation': datetime.now().isoformat(),
279
+ **self.exportable_settings}
280
+ problems = problems_from_dataset(dataset, bodies)
281
+ if 'theta' in dataset.coords:
282
+ results = self.solve_all(problems, keep_details=True, method=method, n_jobs=n_jobs, _check_wavelength=_check_wavelength)
283
+ kochin = kochin_data_array(results, dataset.coords['theta'])
284
+ dataset = assemble_dataset(results, attrs=attrs, **kwargs)
285
+ dataset.update(kochin)
286
+ else:
287
+ results = self.solve_all(problems, keep_details=False, method=method, n_jobs=n_jobs, _check_wavelength=_check_wavelength)
288
+ dataset = assemble_dataset(results, attrs=attrs, **kwargs)
289
+ return dataset
290
+
291
+
292
+ def compute_potential(self, points, result):
293
+ """Compute the value of the potential at given points for a previously solved potential flow problem.
294
+
295
+ Parameters
296
+ ----------
297
+ points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or cpt.Mesh or cpt.CollectionOfMeshes object
298
+ Coordinates of the point(s) at which the potential should be computed
299
+ result: LinearPotentialFlowResult
300
+ The return of the BEM solver
301
+
302
+ Returns
303
+ -------
304
+ complex-valued array of shape (1,) or (N,) or (nx, ny, nz) or (mesh.nb_faces,) depending of the kind of input
305
+ The value of the potential at the points
306
+
307
+ Raises
308
+ ------
309
+ Exception: if the :code:`LinearPotentialFlowResult` object given as input does not contain the source distribution.
310
+ """
311
+ points, output_shape = _normalize_points(points, keep_mesh=True)
312
+ if result.sources is None:
313
+ raise Exception(f"""The values of the sources of {result} cannot been found.
314
+ They probably have not been stored by the solver because the option keep_details=True have not been set or the direct method has been used.
315
+ Please re-run the resolution with the indirect method and keep_details=True.""")
316
+
317
+ S, _ = self.green_function.evaluate(points, result.body.mesh_including_lid, result.free_surface, result.water_depth, result.encounter_wavenumber)
318
+ potential = S @ result.sources # Sum the contributions of all panels in the mesh
319
+ return potential.reshape(output_shape)
320
+
321
+ def _compute_potential_gradient(self, points, result):
322
+ points, output_shape = _normalize_points(points, keep_mesh=True)
323
+
324
+ if result.sources is None:
325
+ raise Exception(f"""The values of the sources of {result} cannot been found.
326
+ They probably have not been stored by the solver because the option keep_details=True have not been set.
327
+ Please re-run the resolution with this option.""")
328
+
329
+ _, gradG = self.green_function.evaluate(points, result.body.mesh_including_lid, result.free_surface, result.water_depth, result.encounter_wavenumber,
330
+ early_dot_product=False)
331
+ velocities = np.einsum('ijk,j->ik', gradG, result.sources) # Sum the contributions of all panels in the mesh
332
+ return velocities.reshape((*output_shape, 3))
333
+
334
+ def compute_velocity(self, points, result):
335
+ """Compute the value of the velocity vector at given points for a previously solved potential flow problem.
336
+
337
+ Parameters
338
+ ----------
339
+ points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or cpt.Mesh or cpt.CollectionOfMeshes object
340
+ Coordinates of the point(s) at which the velocity should be computed
341
+ result: LinearPotentialFlowResult
342
+ The return of the BEM solver
343
+
344
+ Returns
345
+ -------
346
+ complex-valued array of shape (3,) or (N,, 3) or (nx, ny, nz, 3) or (mesh.nb_faces, 3) depending of the kind of input
347
+ The value of the velocity at the points
348
+
349
+ Raises
350
+ ------
351
+ Exception: if the :code:`LinearPotentialFlowResult` object given as input does not contain the source distribution.
352
+ """
353
+ nabla_phi = self._compute_potential_gradient(points, result)
354
+ if result.forward_speed != 0.0:
355
+ nabla_phi[..., 0] -= result.forward_speed
356
+ return nabla_phi
357
+
358
+ def compute_pressure(self, points, result):
359
+ """Compute the value of the pressure at given points for a previously solved potential flow problem.
360
+
361
+ Parameters
362
+ ----------
363
+ points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or cpt.Mesh or cpt.CollectionOfMeshes object
364
+ Coordinates of the point(s) at which the pressure should be computed
365
+ result: LinearPotentialFlowResult
366
+ The return of the BEM solver
367
+
368
+ Returns
369
+ -------
370
+ complex-valued array of shape (1,) or (N,) or (nx, ny, nz) or (mesh.nb_faces,) depending of the kind of input
371
+ The value of the pressure at the points
372
+
373
+ Raises
374
+ ------
375
+ Exception: if the :code:`LinearPotentialFlowResult` object given as input does not contain the source distribution.
376
+ """
377
+ if result.forward_speed != 0:
378
+ pressure = 1j * result.encounter_omega * result.rho * self.compute_potential(points, result)
379
+ nabla_phi = self._compute_potential_gradient(points, result)
380
+ pressure += result.rho * result.forward_speed * nabla_phi[..., 0]
381
+ else:
382
+ pressure = 1j * result.omega * result.rho * self.compute_potential(points, result)
383
+ return pressure
384
+
385
+
386
+ def compute_free_surface_elevation(self, points, result):
387
+ """Compute the value of the free surface elevation at given points for a previously solved potential flow problem.
388
+
389
+ Parameters
390
+ ----------
391
+ points: array of shape (2,) or (N, 2), or 2-ple of arrays returned by meshgrid, or cpt.Mesh or cpt.CollectionOfMeshes object
392
+ Coordinates of the point(s) at which the free surface elevation should be computed
393
+ result: LinearPotentialFlowResult
394
+ The return of the BEM solver
395
+
396
+ Returns
397
+ -------
398
+ complex-valued array of shape (1,) or (N,) or (nx, ny, nz) or (mesh.nb_faces,) depending of the kind of input
399
+ The value of the free surface elevation at the points
400
+
401
+ Raises
402
+ ------
403
+ Exception: if the :code:`LinearPotentialFlowResult` object given as input does not contain the source distribution.
404
+ """
405
+ points, output_shape = _normalize_free_surface_points(points, keep_mesh=True)
406
+
407
+ if result.forward_speed != 0:
408
+ fs_elevation = -1/result.g * (-1j*result.encounter_omega) * self.compute_potential(points, result)
409
+ nabla_phi = self._compute_potential_gradient(points, result)
410
+ fs_elevation += -1/result.g * result.forward_speed * nabla_phi[..., 0]
411
+ else:
412
+ fs_elevation = -1/result.g * (-1j*result.omega) * self.compute_potential(points, result)
413
+
414
+ return fs_elevation.reshape(output_shape)
415
+
416
+
417
+ ## Legacy
418
+
419
+ def get_potential_on_mesh(self, result, mesh, chunk_size=50):
420
+ """Compute the potential on a mesh for the potential field of a previously solved problem.
421
+ Since the interaction matrix does not need to be computed in full to compute the matrix-vector product,
422
+ only a few lines are evaluated at a time to reduce the memory cost of the operation.
423
+
424
+ The newer method :code:`compute_potential` should be preferred in the future.
425
+
426
+ Parameters
427
+ ----------
428
+ result : LinearPotentialFlowResult
429
+ the return of the BEM solver
430
+ mesh : Mesh or CollectionOfMeshes
431
+ a mesh
432
+ chunk_size: int, optional
433
+ Number of lines to compute in the matrix.
434
+ (legacy, should be passed as an engine setting instead).
435
+
436
+ Returns
437
+ -------
438
+ array of shape (mesh.nb_faces,)
439
+ potential on the faces of the mesh
440
+
441
+ Raises
442
+ ------
443
+ Exception: if the :code:`Result` object given as input does not contain the source distribution.
444
+ """
445
+ LOG.info(f"Compute potential on {mesh.name} for {result}.")
446
+
447
+ if result.sources is None:
448
+ raise Exception(f"""The values of the sources of {result} cannot been found.
449
+ They probably have not been stored by the solver because the option keep_details=True have not been set or the direct method has been used.
450
+ Please re-run the resolution with the indirect method and keep_details=True.""")
451
+
452
+ if chunk_size > mesh.nb_faces:
453
+ S = self.engine.build_S_matrix(
454
+ mesh,
455
+ result.body.mesh_including_lid,
456
+ result.free_surface, result.water_depth, result.wavenumber,
457
+ self.green_function
458
+ )
459
+ phi = S @ result.sources
460
+
461
+ else:
462
+ phi = np.empty((mesh.nb_faces,), dtype=np.complex128)
463
+ for i in range(0, mesh.nb_faces, chunk_size):
464
+ faces_to_extract = list(range(i, min(i+chunk_size, mesh.nb_faces)))
465
+ S = self.engine.build_S_matrix(
466
+ mesh.extract_faces(faces_to_extract),
467
+ result.body.mesh_including_lid,
468
+ result.free_surface, result.water_depth, result.wavenumber,
469
+ self.green_function
470
+ )
471
+ phi[i:i+chunk_size] = S @ result.sources
472
+
473
+ LOG.debug(f"Done computing potential on {mesh.name} for {result}.")
474
+
475
+ return phi
476
+
477
+ def get_free_surface_elevation(self, result, free_surface, keep_details=False):
478
+ """Compute the elevation of the free surface on a mesh for a previously solved problem.
479
+
480
+ The newer method :code:`compute_free_surface_elevation` should be preferred in the future.
481
+
482
+ Parameters
483
+ ----------
484
+ result : LinearPotentialFlowResult
485
+ the return of the solver
486
+ free_surface : FreeSurface
487
+ a meshed free surface
488
+ keep_details : bool, optional
489
+ if True, keep the free surface elevation in the LinearPotentialFlowResult (default:False)
490
+
491
+ Returns
492
+ -------
493
+ array of shape (free_surface.nb_faces,)
494
+ the free surface elevation on each faces of the meshed free surface
495
+
496
+ Raises
497
+ ------
498
+ Exception: if the :code:`Result` object given as input does not contain the source distribution.
499
+ """
500
+ if result.forward_speed != 0.0:
501
+ raise NotImplementedError("For free surface elevation with forward speed, please use the `compute_free_surface_elevation` method.")
502
+
503
+ fs_elevation = 1j*result.omega/result.g * self.get_potential_on_mesh(result, free_surface.mesh)
504
+ if keep_details:
505
+ result.fs_elevation[free_surface] = fs_elevation
506
+ return fs_elevation
@@ -0,0 +1,4 @@
1
+ # Copyright (C) 2017-2019 Matthieu Ancellin
2
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
3
+
4
+ from capytaine.bodies.bodies import FloatingBody