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