capytaine 2.2.1__cp311-cp311-macosx_14_0_arm64.whl → 2.3__cp311-cp311-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.
- 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 +1 -1
- capytaine/__init__.py +2 -1
- capytaine/bem/airy_waves.py +7 -2
- capytaine/bem/problems_and_results.py +78 -34
- capytaine/bem/solver.py +127 -39
- capytaine/bodies/bodies.py +30 -10
- 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 +18 -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 +186 -115
- capytaine/green_functions/hams.py +204 -0
- capytaine/green_functions/libs/Delhommeau_float32.cpython-311-darwin.so +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cpython-311-darwin.so +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 +257 -113
- capytaine/matrices/linear_solvers.py +1 -1
- capytaine/meshes/clipper.py +1 -0
- capytaine/meshes/collections.py +11 -1
- capytaine/meshes/mesh_like_protocol.py +37 -0
- capytaine/meshes/meshes.py +17 -6
- capytaine/meshes/symmetric.py +11 -2
- 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 +12 -0
- capytaine/tools/timer.py +64 -0
- {capytaine-2.2.1.dist-info → capytaine-2.3.dist-info}/METADATA +9 -2
- capytaine-2.3.dist-info/RECORD +92 -0
- capytaine-2.2.1.dist-info/RECORD +0 -76
- {capytaine-2.2.1.dist-info → capytaine-2.3.dist-info}/LICENSE +0 -0
- {capytaine-2.2.1.dist-info → capytaine-2.3.dist-info}/WHEEL +0 -0
- {capytaine-2.2.1.dist-info → capytaine-2.3.dist-info}/entry_points.txt +0 -0
capytaine/bem/solver.py
CHANGED
|
@@ -9,21 +9,24 @@
|
|
|
9
9
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import os
|
|
12
13
|
import logging
|
|
13
14
|
|
|
14
15
|
import numpy as np
|
|
16
|
+
import pandas as pd
|
|
15
17
|
|
|
16
18
|
from datetime import datetime
|
|
17
19
|
|
|
18
20
|
from rich.progress import track
|
|
19
21
|
|
|
20
|
-
from capytaine.bem.problems_and_results import LinearPotentialFlowProblem
|
|
22
|
+
from capytaine.bem.problems_and_results import LinearPotentialFlowProblem, DiffractionProblem
|
|
21
23
|
from capytaine.green_functions.delhommeau import Delhommeau
|
|
22
24
|
from capytaine.bem.engines import BasicMatrixEngine
|
|
23
25
|
from capytaine.io.xarray import problems_from_dataset, assemble_dataset, kochin_data_array
|
|
24
26
|
from capytaine.tools.optional_imports import silently_import_optional_dependency
|
|
25
27
|
from capytaine.tools.lists_of_points import _normalize_points, _normalize_free_surface_points
|
|
26
28
|
from capytaine.tools.symbolic_multiplication import supporting_symbolic_multiplication
|
|
29
|
+
from capytaine.tools.timer import Timer
|
|
27
30
|
|
|
28
31
|
LOG = logging.getLogger(__name__)
|
|
29
32
|
|
|
@@ -39,24 +42,39 @@ class BEMSolver:
|
|
|
39
42
|
engine: MatrixEngine, optional
|
|
40
43
|
Object handling the building of matrices and the resolution of linear systems with these matrices.
|
|
41
44
|
(default: :class:`~capytaine.bem.engines.BasicMatrixEngine`)
|
|
45
|
+
method: string, optional
|
|
46
|
+
select boundary integral equation used to solve the problems.
|
|
47
|
+
Accepted values: "indirect" (as in e.g. Nemoh), "direct" (as in e.g. WAMIT)
|
|
48
|
+
Default value: "indirect"
|
|
42
49
|
|
|
43
50
|
Attributes
|
|
44
51
|
----------
|
|
52
|
+
timer: dict[str, Timer]
|
|
53
|
+
Storing the time spent on each subtasks of the resolution
|
|
45
54
|
exportable_settings : dict
|
|
46
55
|
Settings of the solver that can be saved to reinit the same solver later.
|
|
47
56
|
"""
|
|
48
57
|
|
|
49
|
-
def __init__(self, *, green_function=None, engine=None):
|
|
58
|
+
def __init__(self, *, green_function=None, engine=None, method="indirect"):
|
|
50
59
|
self.green_function = Delhommeau() if green_function is None else green_function
|
|
51
60
|
self.engine = BasicMatrixEngine() if engine is None else engine
|
|
52
61
|
|
|
62
|
+
if method.lower() not in {"direct", "indirect"}:
|
|
63
|
+
raise ValueError(f"Unrecognized method when initializing solver: {repr(method)}. Expected \"direct\" or \"indirect\".")
|
|
64
|
+
self.method = method.lower()
|
|
65
|
+
|
|
66
|
+
self.timer = {"Solve total": Timer(), " Green function": Timer(), " Linear solver": Timer()}
|
|
67
|
+
|
|
68
|
+
self.solve = self.timer["Solve total"].wraps_function(self.solve)
|
|
69
|
+
|
|
53
70
|
try:
|
|
54
71
|
self.exportable_settings = {
|
|
55
72
|
**self.green_function.exportable_settings,
|
|
56
|
-
**self.engine.exportable_settings
|
|
73
|
+
**self.engine.exportable_settings,
|
|
74
|
+
"method": self.method,
|
|
57
75
|
}
|
|
58
76
|
except AttributeError:
|
|
59
|
-
|
|
77
|
+
self.exportable_settings = {}
|
|
60
78
|
|
|
61
79
|
def __str__(self):
|
|
62
80
|
return f"BEMSolver(engine={self.engine}, green_function={self.green_function})"
|
|
@@ -64,6 +82,15 @@ class BEMSolver:
|
|
|
64
82
|
def __repr__(self):
|
|
65
83
|
return self.__str__()
|
|
66
84
|
|
|
85
|
+
def timer_summary(self):
|
|
86
|
+
return pd.DataFrame([
|
|
87
|
+
{
|
|
88
|
+
"task": name,
|
|
89
|
+
"total": self.timer[name].total,
|
|
90
|
+
"nb_calls": self.timer[name].nb_timings,
|
|
91
|
+
"mean": self.timer[name].mean
|
|
92
|
+
} for name in self.timer]).set_index("task")
|
|
93
|
+
|
|
67
94
|
def _repr_pretty_(self, p, cycle):
|
|
68
95
|
p.text(self.__str__())
|
|
69
96
|
|
|
@@ -71,7 +98,7 @@ class BEMSolver:
|
|
|
71
98
|
def from_exported_settings(settings):
|
|
72
99
|
raise NotImplementedError
|
|
73
100
|
|
|
74
|
-
def solve(self, problem, method=
|
|
101
|
+
def solve(self, problem, method=None, keep_details=True, _check_wavelength=True):
|
|
75
102
|
"""Solve the linear potential flow problem.
|
|
76
103
|
|
|
77
104
|
Parameters
|
|
@@ -79,7 +106,9 @@ class BEMSolver:
|
|
|
79
106
|
problem: LinearPotentialFlowProblem
|
|
80
107
|
the problem to be solved
|
|
81
108
|
method: string, optional
|
|
82
|
-
select boundary integral
|
|
109
|
+
select boundary integral equation used to solve the problem.
|
|
110
|
+
It is recommended to set the method more globally when initializing the solver.
|
|
111
|
+
If provided here, the value in argument of `solve` overrides the global one.
|
|
83
112
|
keep_details: bool, optional
|
|
84
113
|
if True, store the sources and the potential on the floating body in the output object
|
|
85
114
|
(default: True)
|
|
@@ -98,33 +127,54 @@ class BEMSolver:
|
|
|
98
127
|
self._check_wavelength_and_mesh_resolution([problem])
|
|
99
128
|
self._check_wavelength_and_irregular_frequencies([problem])
|
|
100
129
|
|
|
130
|
+
if isinstance(problem, DiffractionProblem) and float(problem.encounter_omega) in {0.0, np.inf}:
|
|
131
|
+
raise ValueError("Diffraction problems at zero or infinite frequency are not defined")
|
|
132
|
+
# This error used to be raised when initializing the problem.
|
|
133
|
+
# It is now raised here, in order to be catchable by
|
|
134
|
+
# _solve_and_catch_errors, such that batch resolution
|
|
135
|
+
# can include this kind of problems without the full batch
|
|
136
|
+
# failing.
|
|
137
|
+
# Note that if this error was not raised here, the resolution
|
|
138
|
+
# would still fail with a less explicit error message.
|
|
139
|
+
|
|
101
140
|
if problem.forward_speed != 0.0:
|
|
102
141
|
omega, wavenumber = problem.encounter_omega, problem.encounter_wavenumber
|
|
103
142
|
else:
|
|
104
143
|
omega, wavenumber = problem.omega, problem.wavenumber
|
|
105
144
|
|
|
106
145
|
linear_solver = supporting_symbolic_multiplication(self.engine.linear_solver)
|
|
146
|
+
method = method if method is not None else self.method
|
|
107
147
|
if (method == 'direct'):
|
|
108
148
|
if problem.forward_speed != 0.0:
|
|
109
149
|
raise NotImplementedError("Direct solver is not able to solve problems with forward speed.")
|
|
110
150
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
151
|
+
with self.timer[" Green function"]:
|
|
152
|
+
S, D = self.engine.build_matrices(
|
|
153
|
+
problem.body.mesh_including_lid, problem.body.mesh_including_lid,
|
|
154
|
+
problem.free_surface, problem.water_depth, wavenumber,
|
|
155
|
+
self.green_function, adjoint_double_layer=False
|
|
156
|
+
)
|
|
157
|
+
rhs = S @ problem.boundary_condition
|
|
158
|
+
with self.timer[" Linear solver"]:
|
|
159
|
+
potential = linear_solver(D, rhs)
|
|
160
|
+
if not potential.shape == problem.boundary_condition.shape:
|
|
161
|
+
raise ValueError(f"Error in linear solver of {self.engine}: the shape of the output ({potential.shape}) "
|
|
162
|
+
f"does not match the expected shape ({problem.boundary_condition.shape})")
|
|
118
163
|
pressure = 1j * omega * problem.rho * potential
|
|
119
164
|
sources = None
|
|
120
165
|
else:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
166
|
+
with self.timer[" Green function"]:
|
|
167
|
+
S, K = self.engine.build_matrices(
|
|
168
|
+
problem.body.mesh_including_lid, problem.body.mesh_including_lid,
|
|
169
|
+
problem.free_surface, problem.water_depth, wavenumber,
|
|
170
|
+
self.green_function, adjoint_double_layer=True
|
|
171
|
+
)
|
|
126
172
|
|
|
127
|
-
|
|
173
|
+
with self.timer[" Linear solver"]:
|
|
174
|
+
sources = linear_solver(K, problem.boundary_condition)
|
|
175
|
+
if not sources.shape == problem.boundary_condition.shape:
|
|
176
|
+
raise ValueError(f"Error in linear solver of {self.engine}: the shape of the output ({sources.shape}) "
|
|
177
|
+
f"does not match the expected shape ({problem.boundary_condition.shape})")
|
|
128
178
|
potential = S @ sources
|
|
129
179
|
pressure = 1j * omega * problem.rho * potential
|
|
130
180
|
if problem.forward_speed != 0.0:
|
|
@@ -145,7 +195,17 @@ class BEMSolver:
|
|
|
145
195
|
|
|
146
196
|
return result
|
|
147
197
|
|
|
148
|
-
def
|
|
198
|
+
def _solve_and_catch_errors(self, problem, *args, **kwargs):
|
|
199
|
+
"""Same as BEMSolver.solve() but returns a
|
|
200
|
+
FailedLinearPotentialFlowResult when the resolution failed."""
|
|
201
|
+
try:
|
|
202
|
+
res = self.solve(problem, *args, **kwargs)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
LOG.info(f"Skipped {problem}\nbecause of {repr(e)}")
|
|
205
|
+
res = problem.make_failed_results_container(e)
|
|
206
|
+
return res
|
|
207
|
+
|
|
208
|
+
def solve_all(self, problems, *, method=None, n_jobs=1, progress_bar=None, _check_wavelength=True, **kwargs):
|
|
149
209
|
"""Solve several problems.
|
|
150
210
|
Optional keyword arguments are passed to `BEMSolver.solve`.
|
|
151
211
|
|
|
@@ -154,12 +214,17 @@ class BEMSolver:
|
|
|
154
214
|
problems: list of LinearPotentialFlowProblem
|
|
155
215
|
several problems to be solved
|
|
156
216
|
method: string, optional
|
|
157
|
-
select boundary integral
|
|
217
|
+
select boundary integral equation used to solve the problems.
|
|
218
|
+
It is recommended to set the method more globally when initializing the solver.
|
|
219
|
+
If provided here, the value in argument of `solve_all` overrides the global one.
|
|
158
220
|
n_jobs: int, optional (default: 1)
|
|
159
221
|
the number of jobs to run in parallel using the optional dependency `joblib`
|
|
160
222
|
By defaults: do not use joblib and solve sequentially.
|
|
161
|
-
progress_bar: bool, optional
|
|
162
|
-
Display a progress bar while solving
|
|
223
|
+
progress_bar: bool, optional
|
|
224
|
+
Display a progress bar while solving.
|
|
225
|
+
If no value is provided to this method directly,
|
|
226
|
+
check whether the environment variable `CAPYTAINE_PROGRESS_BAR` is defined
|
|
227
|
+
and otherwise default to True.
|
|
163
228
|
_check_wavelength: bool, optional (default: True)
|
|
164
229
|
If True, the frequencies are compared to the mesh resolution and
|
|
165
230
|
the estimated first irregular frequency to warn the user.
|
|
@@ -173,11 +238,23 @@ class BEMSolver:
|
|
|
173
238
|
self._check_wavelength_and_mesh_resolution(problems)
|
|
174
239
|
self._check_wavelength_and_irregular_frequencies(problems)
|
|
175
240
|
|
|
241
|
+
if progress_bar is None:
|
|
242
|
+
if "CAPYTAINE_PROGRESS_BAR" in os.environ:
|
|
243
|
+
env_var = os.environ["CAPYTAINE_PROGRESS_BAR"].lower()
|
|
244
|
+
if env_var in {'true', '1', 't'}:
|
|
245
|
+
progress_bar = True
|
|
246
|
+
elif env_var in {'false', '0', 'f'}:
|
|
247
|
+
progress_bar = False
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError("Invalid value '{}' for the environment variable CAPYTAINE_PROGRESS_BAR.".format(os.environ["CAPYTAINE_PROGRESS_BAR"]))
|
|
250
|
+
else:
|
|
251
|
+
progress_bar = True
|
|
252
|
+
|
|
176
253
|
if n_jobs == 1: # force sequential resolution
|
|
177
254
|
problems = sorted(problems)
|
|
178
255
|
if progress_bar:
|
|
179
256
|
problems = track(problems, total=len(problems), description="Solving BEM problems")
|
|
180
|
-
|
|
257
|
+
results = [self._solve_and_catch_errors(pb, method=method, _check_wavelength=False, **kwargs) for pb in problems]
|
|
181
258
|
else:
|
|
182
259
|
joblib = silently_import_optional_dependency("joblib")
|
|
183
260
|
if joblib is None:
|
|
@@ -190,7 +267,8 @@ class BEMSolver:
|
|
|
190
267
|
total=len(groups_of_problems),
|
|
191
268
|
description=f"Solving BEM problems with {n_jobs} threads:")
|
|
192
269
|
results = [res for grp in groups_of_results for res in grp] # flatten the nested list
|
|
193
|
-
|
|
270
|
+
LOG.info("Solver timer summary:\n%s", self.timer_summary())
|
|
271
|
+
return results
|
|
194
272
|
|
|
195
273
|
@staticmethod
|
|
196
274
|
def _check_wavelength_and_mesh_resolution(problems):
|
|
@@ -225,7 +303,8 @@ class BEMSolver:
|
|
|
225
303
|
"""Display a warning if some of the problems might encounter irregular frequencies."""
|
|
226
304
|
LOG.debug("Check wavelength with estimated irregular frequency.")
|
|
227
305
|
risky_problems = [pb for pb in problems
|
|
228
|
-
if pb.
|
|
306
|
+
if pb.free_surface != np.inf and
|
|
307
|
+
pb.body.first_irregular_frequency_estimate(g=pb.g) < pb.omega < np.inf]
|
|
229
308
|
nb_risky_problems = len(risky_problems)
|
|
230
309
|
if nb_risky_problems >= 1:
|
|
231
310
|
if any(pb.body.lid_mesh is None for pb in problems):
|
|
@@ -250,7 +329,7 @@ class BEMSolver:
|
|
|
250
329
|
+ recommendation
|
|
251
330
|
)
|
|
252
331
|
|
|
253
|
-
def fill_dataset(self, dataset, bodies, *, method=
|
|
332
|
+
def fill_dataset(self, dataset, bodies, *, method=None, n_jobs=1, _check_wavelength=True, progress_bar=None, **kwargs):
|
|
254
333
|
"""Solve a set of problems defined by the coordinates of an xarray dataset.
|
|
255
334
|
|
|
256
335
|
Parameters
|
|
@@ -261,12 +340,17 @@ class BEMSolver:
|
|
|
261
340
|
The body or bodies involved in the problems
|
|
262
341
|
They should all have different names.
|
|
263
342
|
method: string, optional
|
|
264
|
-
select boundary integral
|
|
343
|
+
select boundary integral equation used to solve the problems.
|
|
344
|
+
It is recommended to set the method more globally when initializing the solver.
|
|
345
|
+
If provided here, the value in argument of `fill_dataset` overrides the global one.
|
|
265
346
|
n_jobs: int, optional (default: 1)
|
|
266
347
|
the number of jobs to run in parallel using the optional dependency `joblib`
|
|
267
348
|
By defaults: do not use joblib and solve sequentially.
|
|
268
|
-
progress_bar: bool, optional
|
|
269
|
-
Display a progress bar while solving
|
|
349
|
+
progress_bar: bool, optional
|
|
350
|
+
Display a progress bar while solving.
|
|
351
|
+
If no value is provided to this method directly,
|
|
352
|
+
check whether the environment variable `CAPYTAINE_PROGRESS_BAR` is defined
|
|
353
|
+
and otherwise default to True.
|
|
270
354
|
_check_wavelength: bool, optional (default: True)
|
|
271
355
|
If True, the frequencies are compared to the mesh resolution and
|
|
272
356
|
the estimated first irregular frequency to warn the user.
|
|
@@ -277,14 +361,16 @@ class BEMSolver:
|
|
|
277
361
|
"""
|
|
278
362
|
attrs = {'start_of_computation': datetime.now().isoformat(),
|
|
279
363
|
**self.exportable_settings}
|
|
364
|
+
if method is not None: # Overrides the method in self.exportable_settings
|
|
365
|
+
attrs["method"] = method
|
|
280
366
|
problems = problems_from_dataset(dataset, bodies)
|
|
281
367
|
if 'theta' in dataset.coords:
|
|
282
|
-
results = self.solve_all(problems, keep_details=True, method=method, n_jobs=n_jobs, _check_wavelength=_check_wavelength)
|
|
368
|
+
results = self.solve_all(problems, keep_details=True, method=method, n_jobs=n_jobs, _check_wavelength=_check_wavelength, progress_bar=progress_bar)
|
|
283
369
|
kochin = kochin_data_array(results, dataset.coords['theta'])
|
|
284
370
|
dataset = assemble_dataset(results, attrs=attrs, **kwargs)
|
|
285
371
|
dataset.update(kochin)
|
|
286
372
|
else:
|
|
287
|
-
results = self.solve_all(problems, keep_details=False, method=method, n_jobs=n_jobs, _check_wavelength=_check_wavelength)
|
|
373
|
+
results = self.solve_all(problems, keep_details=False, method=method, n_jobs=n_jobs, _check_wavelength=_check_wavelength, progress_bar=progress_bar)
|
|
288
374
|
dataset = assemble_dataset(results, attrs=attrs, **kwargs)
|
|
289
375
|
return dataset
|
|
290
376
|
|
|
@@ -294,7 +380,7 @@ class BEMSolver:
|
|
|
294
380
|
|
|
295
381
|
Parameters
|
|
296
382
|
----------
|
|
297
|
-
points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or
|
|
383
|
+
points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or MeshLike object
|
|
298
384
|
Coordinates of the point(s) at which the potential should be computed
|
|
299
385
|
result: LinearPotentialFlowResult
|
|
300
386
|
The return of the BEM solver
|
|
@@ -314,7 +400,8 @@ class BEMSolver:
|
|
|
314
400
|
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
401
|
Please re-run the resolution with the indirect method and keep_details=True.""")
|
|
316
402
|
|
|
317
|
-
|
|
403
|
+
with self.timer[" Green function"]:
|
|
404
|
+
S, _ = self.green_function.evaluate(points, result.body.mesh_including_lid, result.free_surface, result.water_depth, result.encounter_wavenumber)
|
|
318
405
|
potential = S @ result.sources # Sum the contributions of all panels in the mesh
|
|
319
406
|
return potential.reshape(output_shape)
|
|
320
407
|
|
|
@@ -326,7 +413,8 @@ class BEMSolver:
|
|
|
326
413
|
They probably have not been stored by the solver because the option keep_details=True have not been set.
|
|
327
414
|
Please re-run the resolution with this option.""")
|
|
328
415
|
|
|
329
|
-
|
|
416
|
+
with self.timer[" Green function"]:
|
|
417
|
+
_, gradG = self.green_function.evaluate(points, result.body.mesh_including_lid, result.free_surface, result.water_depth, result.encounter_wavenumber,
|
|
330
418
|
early_dot_product=False)
|
|
331
419
|
velocities = np.einsum('ijk,j->ik', gradG, result.sources) # Sum the contributions of all panels in the mesh
|
|
332
420
|
return velocities.reshape((*output_shape, 3))
|
|
@@ -336,7 +424,7 @@ class BEMSolver:
|
|
|
336
424
|
|
|
337
425
|
Parameters
|
|
338
426
|
----------
|
|
339
|
-
points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or
|
|
427
|
+
points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or MeshLike object
|
|
340
428
|
Coordinates of the point(s) at which the velocity should be computed
|
|
341
429
|
result: LinearPotentialFlowResult
|
|
342
430
|
The return of the BEM solver
|
|
@@ -360,7 +448,7 @@ class BEMSolver:
|
|
|
360
448
|
|
|
361
449
|
Parameters
|
|
362
450
|
----------
|
|
363
|
-
points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or
|
|
451
|
+
points: array of shape (3,) or (N, 3), or 3-ple of arrays returned by meshgrid, or MeshLike object
|
|
364
452
|
Coordinates of the point(s) at which the pressure should be computed
|
|
365
453
|
result: LinearPotentialFlowResult
|
|
366
454
|
The return of the BEM solver
|
|
@@ -388,7 +476,7 @@ class BEMSolver:
|
|
|
388
476
|
|
|
389
477
|
Parameters
|
|
390
478
|
----------
|
|
391
|
-
points: array of shape (2,) or (N, 2), or 2-ple of arrays returned by meshgrid, or
|
|
479
|
+
points: array of shape (2,) or (N, 2), or 2-ple of arrays returned by meshgrid, or MeshLike object
|
|
392
480
|
Coordinates of the point(s) at which the free surface elevation should be computed
|
|
393
481
|
result: LinearPotentialFlowResult
|
|
394
482
|
The return of the BEM solver
|
|
@@ -427,7 +515,7 @@ class BEMSolver:
|
|
|
427
515
|
----------
|
|
428
516
|
result : LinearPotentialFlowResult
|
|
429
517
|
the return of the BEM solver
|
|
430
|
-
mesh :
|
|
518
|
+
mesh : MeshLike
|
|
431
519
|
a mesh
|
|
432
520
|
chunk_size: int, optional
|
|
433
521
|
Number of lines to compute in the matrix.
|
capytaine/bodies/bodies.py
CHANGED
|
@@ -10,6 +10,7 @@ from functools import cached_property, lru_cache
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import xarray as xr
|
|
12
12
|
|
|
13
|
+
from capytaine.meshes.mesh_like_protocol import MeshLike
|
|
13
14
|
from capytaine.meshes.collections import CollectionOfMeshes
|
|
14
15
|
from capytaine.meshes.geometry import Abstract3DObject, ClippableMixin, Plane, inplace_transformation
|
|
15
16
|
from capytaine.meshes.properties import connected_components, connected_components_of_waterline
|
|
@@ -39,10 +40,10 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
39
40
|
|
|
40
41
|
Parameters
|
|
41
42
|
----------
|
|
42
|
-
mesh :
|
|
43
|
+
mesh : MeshLike, optional
|
|
43
44
|
the mesh describing the geometry of the hull of the floating body.
|
|
44
45
|
If none is given, a empty one is created.
|
|
45
|
-
lid_mesh :
|
|
46
|
+
lid_mesh : MeshLike or None, optional
|
|
46
47
|
a mesh of an internal lid for irregular frequencies removal.
|
|
47
48
|
Unlike the mesh of the hull, no dof is defined on the lid_mesh.
|
|
48
49
|
If none is given, none is used when solving the Boundary Integral Equation.
|
|
@@ -69,7 +70,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
69
70
|
from capytaine.io.meshio import load_from_meshio
|
|
70
71
|
self.mesh = load_from_meshio(mesh)
|
|
71
72
|
|
|
72
|
-
elif isinstance(mesh,
|
|
73
|
+
elif isinstance(mesh, MeshLike):
|
|
73
74
|
self.mesh = mesh
|
|
74
75
|
|
|
75
76
|
else:
|
|
@@ -87,7 +88,10 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
87
88
|
if name is None and mesh is None:
|
|
88
89
|
self.name = "dummy_body"
|
|
89
90
|
elif name is None:
|
|
90
|
-
|
|
91
|
+
if hasattr(self.mesh, "name"):
|
|
92
|
+
self.name = self.mesh.name
|
|
93
|
+
else:
|
|
94
|
+
self.name = "anonymous_body"
|
|
91
95
|
else:
|
|
92
96
|
self.name = name
|
|
93
97
|
|
|
@@ -97,7 +101,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
97
101
|
else:
|
|
98
102
|
self.center_of_mass = None
|
|
99
103
|
|
|
100
|
-
if self.mesh.nb_vertices > 0 and self.mesh.nb_faces > 0:
|
|
104
|
+
if hasattr(self.mesh, "heal_mesh") and self.mesh.nb_vertices > 0 and self.mesh.nb_faces > 0:
|
|
101
105
|
self.mesh.heal_mesh()
|
|
102
106
|
|
|
103
107
|
if dofs is None:
|
|
@@ -112,6 +116,8 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
112
116
|
|
|
113
117
|
LOG.info(f"New floating body: {self.__str__()}.")
|
|
114
118
|
|
|
119
|
+
self._check_dofs_shape_consistency()
|
|
120
|
+
|
|
115
121
|
@staticmethod
|
|
116
122
|
def from_meshio(mesh, name=None) -> 'FloatingBody':
|
|
117
123
|
"""Create a FloatingBody from a meshio mesh object.
|
|
@@ -135,7 +141,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
135
141
|
@cached_property
|
|
136
142
|
def mesh_including_lid(self):
|
|
137
143
|
if self.lid_mesh is not None:
|
|
138
|
-
return
|
|
144
|
+
return self.mesh.join_meshes(self.lid_mesh)
|
|
139
145
|
else:
|
|
140
146
|
return self.mesh
|
|
141
147
|
|
|
@@ -264,6 +270,14 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
264
270
|
coords={'influenced_dof': list(self.dofs), 'radiating_dof': list(self.dofs)},
|
|
265
271
|
)
|
|
266
272
|
|
|
273
|
+
def _check_dofs_shape_consistency(self):
|
|
274
|
+
for dof_name, dof in self.dofs.items():
|
|
275
|
+
if np.array(dof).shape != (self.mesh.nb_faces, 3):
|
|
276
|
+
raise ValueError(f"The array defining the dof {dof_name} of body {self.name} does not have the expected shape.\n"
|
|
277
|
+
f"Expected shape: ({self.mesh.nb_faces}, 3)\n"
|
|
278
|
+
f" Actual shape: {dof.shape}")
|
|
279
|
+
|
|
280
|
+
|
|
267
281
|
###################
|
|
268
282
|
# Hydrostatics #
|
|
269
283
|
###################
|
|
@@ -756,14 +770,14 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
756
770
|
if name is None:
|
|
757
771
|
name = "+".join(body.name for body in bodies)
|
|
758
772
|
meshes = CollectionOfMeshes(
|
|
759
|
-
[body.mesh for body in bodies],
|
|
773
|
+
[body.mesh.copy() for body in bodies],
|
|
760
774
|
name=f"{name}_mesh"
|
|
761
775
|
)
|
|
762
776
|
if all(body.lid_mesh is None for body in bodies):
|
|
763
777
|
lid_meshes = None
|
|
764
778
|
else:
|
|
765
779
|
lid_meshes = CollectionOfMeshes(
|
|
766
|
-
[body.lid_mesh for body in bodies if body.lid_mesh is not None],
|
|
780
|
+
[body.lid_mesh.copy() for body in bodies if body.lid_mesh is not None],
|
|
767
781
|
name=f"{name}_lid_mesh"
|
|
768
782
|
)
|
|
769
783
|
dofs = FloatingBody.combine_dofs(bodies)
|
|
@@ -796,6 +810,8 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
796
810
|
@staticmethod
|
|
797
811
|
def combine_dofs(bodies) -> dict:
|
|
798
812
|
"""Combine the degrees of freedom of several bodies."""
|
|
813
|
+
for body in bodies:
|
|
814
|
+
body._check_dofs_shape_consistency()
|
|
799
815
|
dofs = {}
|
|
800
816
|
cum_nb_faces = accumulate(chain([0], (body.mesh.nb_faces for body in bodies)))
|
|
801
817
|
total_nb_faces = sum(body.mesh.nb_faces for body in bodies)
|
|
@@ -823,6 +839,8 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
823
839
|
name : str, optional
|
|
824
840
|
a name for the new copy
|
|
825
841
|
"""
|
|
842
|
+
self._check_dofs_shape_consistency()
|
|
843
|
+
|
|
826
844
|
new_body = copy.deepcopy(self)
|
|
827
845
|
if name is None:
|
|
828
846
|
new_body.name = f"copy_of_{self.name}"
|
|
@@ -878,9 +896,9 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
878
896
|
raise NotImplementedError # TODO
|
|
879
897
|
|
|
880
898
|
if return_index:
|
|
881
|
-
new_mesh, id_v =
|
|
899
|
+
new_mesh, id_v = self.mesh.extract_faces(id_faces_to_extract, return_index)
|
|
882
900
|
else:
|
|
883
|
-
new_mesh =
|
|
901
|
+
new_mesh = self.mesh.extract_faces(id_faces_to_extract, return_index)
|
|
884
902
|
new_body = FloatingBody(new_mesh)
|
|
885
903
|
LOG.info(f"Extract floating body from {self.name}.")
|
|
886
904
|
|
|
@@ -995,6 +1013,8 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
995
1013
|
|
|
996
1014
|
@inplace_transformation
|
|
997
1015
|
def clip(self, plane):
|
|
1016
|
+
self._check_dofs_shape_consistency()
|
|
1017
|
+
|
|
998
1018
|
# Clip mesh
|
|
999
1019
|
LOG.info(f"Clipping {self.name} with respect to {plane}")
|
|
1000
1020
|
self.mesh.clip(plane)
|
|
@@ -101,6 +101,8 @@ class RectangularParallelepiped(FloatingBody):
|
|
|
101
101
|
translation_symmetry=translational_symmetry, reflection_symmetry=reflection_symmetry,
|
|
102
102
|
name=f"{name}_mesh")
|
|
103
103
|
|
|
104
|
+
self.geometric_center = np.asarray(center, dtype=float)
|
|
105
|
+
|
|
104
106
|
FloatingBody.__init__(self, mesh=mesh, name=name)
|
|
105
107
|
|
|
106
108
|
class OpenRectangularParallelepiped(RectangularParallelepiped):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
build/
|