capytaine 2.0__cp38-cp38-win_amd64.whl → 2.2__cp38-cp38-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- capytaine/__about__.py +3 -3
- capytaine/__init__.py +16 -14
- capytaine/bem/airy_waves.py +4 -6
- capytaine/bem/engines.py +146 -25
- capytaine/bem/problems_and_results.py +217 -106
- capytaine/bem/solver.py +179 -47
- capytaine/bodies/__init__.py +1 -1
- capytaine/bodies/bodies.py +207 -39
- capytaine/bodies/predefined/__init__.py +0 -2
- capytaine/bodies/predefined/cylinders.py +0 -2
- capytaine/bodies/predefined/rectangles.py +0 -2
- capytaine/bodies/predefined/spheres.py +0 -2
- capytaine/green_functions/abstract_green_function.py +0 -3
- capytaine/green_functions/delhommeau.py +225 -63
- capytaine/green_functions/libs/Delhommeau_float32.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/Delhommeau_float32.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp38-win_amd64.pyd +0 -0
- capytaine/io/bemio.py +17 -16
- capytaine/io/legacy.py +52 -20
- capytaine/io/mesh_loaders.py +49 -27
- capytaine/io/mesh_writers.py +1 -3
- capytaine/io/meshio.py +4 -1
- capytaine/io/xarray.py +73 -35
- capytaine/matrices/__init__.py +0 -2
- capytaine/matrices/block.py +23 -2
- capytaine/matrices/block_toeplitz.py +0 -2
- capytaine/matrices/builders.py +2 -4
- capytaine/matrices/linear_solvers.py +84 -7
- capytaine/matrices/low_rank.py +0 -2
- capytaine/meshes/__init__.py +0 -2
- capytaine/meshes/clipper.py +0 -3
- capytaine/meshes/collections.py +49 -20
- capytaine/meshes/geometry.py +3 -6
- capytaine/meshes/meshes.py +170 -81
- capytaine/meshes/predefined/__init__.py +0 -1
- capytaine/meshes/predefined/cylinders.py +48 -7
- capytaine/meshes/predefined/rectangles.py +43 -10
- capytaine/meshes/predefined/spheres.py +15 -4
- capytaine/meshes/properties.py +43 -2
- capytaine/meshes/quadratures.py +80 -0
- capytaine/meshes/quality.py +1 -3
- capytaine/meshes/surface_integrals.py +0 -1
- capytaine/meshes/symmetric.py +55 -14
- capytaine/post_pro/free_surfaces.py +4 -7
- capytaine/post_pro/impedance.py +12 -10
- capytaine/post_pro/kochin.py +5 -3
- capytaine/post_pro/rao.py +16 -22
- capytaine/tools/cache_on_disk.py +26 -0
- capytaine/tools/deprecation_handling.py +2 -2
- capytaine/tools/lists_of_points.py +13 -3
- capytaine/tools/lru_cache.py +23 -29
- capytaine/tools/optional_imports.py +0 -2
- capytaine/tools/prony_decomposition.py +0 -3
- capytaine/tools/symbolic_multiplication.py +107 -0
- capytaine/ui/cli.py +7 -27
- capytaine/ui/rich.py +5 -0
- capytaine/ui/vtk/__init__.py +0 -3
- capytaine/ui/vtk/animation.py +28 -8
- capytaine/ui/vtk/body_viewer.py +2 -2
- capytaine/ui/vtk/helpers.py +0 -3
- capytaine/ui/vtk/mesh_viewer.py +0 -3
- capytaine-2.2.dist-info/DELVEWHEEL +2 -0
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/METADATA +32 -14
- capytaine-2.2.dist-info/RECORD +82 -0
- capytaine.libs/{.load-order-capytaine-2.0 → .load-order-capytaine-2.2} +2 -1
- capytaine.libs/libgcc_s_seh-1.dll +0 -0
- capytaine.libs/libgfortran-5.dll +0 -0
- capytaine.libs/libgomp-1.dll +0 -0
- capytaine.libs/libquadmath-0.dll +0 -0
- capytaine.libs/libwinpthread-1.dll +0 -0
- capytaine/green_functions/libDelhommeau/.gitignore +0 -5
- capytaine/green_functions/libDelhommeau/LICENSE +0 -203
- capytaine/green_functions/libDelhommeau/Makefile +0 -123
- capytaine/green_functions/libDelhommeau/README.md +0 -15
- capytaine/green_functions/libDelhommeau/benchmarks/openmp/benchmark_omp.f90 +0 -212
- capytaine/green_functions/libDelhommeau/benchmarks/openmp/display_mesh.py +0 -7
- capytaine/green_functions/libDelhommeau/benchmarks/openmp/read_output.py +0 -82
- capytaine/green_functions/libDelhommeau/benchmarks/profiling/benchmark_profiling.f90 +0 -201
- capytaine/green_functions/libDelhommeau/benchmarks/tabulations/benchmark_tabulation.f90 +0 -87
- capytaine/green_functions/libDelhommeau/examples/minimal/minimal_example.f90 +0 -213
- capytaine/green_functions/libDelhommeau/examples/minimal/minimal_example.py +0 -60
- capytaine/green_functions/libDelhommeau/src/Delhommeau_integrals.f90 +0 -311
- capytaine/green_functions/libDelhommeau/src/Green_Rankine.f90 +0 -148
- capytaine/green_functions/libDelhommeau/src/Green_wave.f90 +0 -303
- capytaine/green_functions/libDelhommeau/src/constants.f90 +0 -16
- capytaine/green_functions/libDelhommeau/src/float32.f90 +0 -7
- capytaine/green_functions/libDelhommeau/src/float64.f90 +0 -7
- capytaine/green_functions/libDelhommeau/src/matrices.f90 +0 -274
- capytaine/green_functions/libDelhommeau/src/old_Prony_decomposition.f90 +0 -636
- capytaine/green_functions/libs/XieDelhommeau_float32.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/XieDelhommeau_float32.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/XieDelhommeau_float64.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/XieDelhommeau_float64.cp38-win_amd64.pyd +0 -0
- capytaine-2.0.dist-info/DELVEWHEEL +0 -2
- capytaine-2.0.dist-info/RECORD +0 -100
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/LICENSE +0 -0
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/WHEEL +0 -0
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# coding: utf-8
|
|
3
1
|
"""Definition of the problems to solve with the BEM solver, and the results of this resolution."""
|
|
4
2
|
# Copyright (C) 2017-2023 Matthieu Ancellin
|
|
5
3
|
# See LICENSE file at <https://github.com/capytaine/capytaine>
|
|
@@ -13,12 +11,14 @@ from scipy.optimize import newton
|
|
|
13
11
|
from capytaine.tools.deprecation_handling import _get_water_depth
|
|
14
12
|
from capytaine.meshes.collections import CollectionOfMeshes
|
|
15
13
|
from capytaine.bem.airy_waves import airy_waves_velocity, froude_krylov_force
|
|
14
|
+
from capytaine.tools.symbolic_multiplication import SymbolicMultiplication
|
|
16
15
|
|
|
17
16
|
LOG = logging.getLogger(__name__)
|
|
18
17
|
|
|
19
18
|
_default_parameters = {'rho': 1000.0, 'g': 9.81, 'omega': 1.0,
|
|
20
|
-
'free_surface': 0.0, 'water_depth': np.
|
|
21
|
-
|
|
19
|
+
'free_surface': 0.0, 'water_depth': np.inf,
|
|
20
|
+
'wave_direction': 0.0, 'forward_speed': 0.0}
|
|
21
|
+
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class LinearPotentialFlowProblem:
|
|
@@ -32,9 +32,9 @@ class LinearPotentialFlowProblem:
|
|
|
32
32
|
body: FloatingBody, optional
|
|
33
33
|
The body interacting with the waves
|
|
34
34
|
free_surface: float, optional
|
|
35
|
-
The position of the free surface (accepted values: 0 and np.
|
|
35
|
+
The position of the free surface (accepted values: 0 and np.inf)
|
|
36
36
|
water_depth: float, optional
|
|
37
|
-
The depth of water in m (default: np.
|
|
37
|
+
The depth of water in m (default: np.inf)
|
|
38
38
|
sea_bottom: float, optional
|
|
39
39
|
The position of the sea bottom (deprecated: please prefer setting water_depth)
|
|
40
40
|
omega: float, optional
|
|
@@ -45,6 +45,8 @@ class LinearPotentialFlowProblem:
|
|
|
45
45
|
The angular wave number of the waves in rad/m
|
|
46
46
|
wavelength: float, optional
|
|
47
47
|
The wave length of the waves in m
|
|
48
|
+
forward_speed: float, optional
|
|
49
|
+
The speed of the body (in m/s, in the x direction, default: 0.0)
|
|
48
50
|
rho: float, optional
|
|
49
51
|
The density of water in kg/m3 (default: 1000.0)
|
|
50
52
|
g: float, optional
|
|
@@ -58,24 +60,45 @@ class LinearPotentialFlowProblem:
|
|
|
58
60
|
free_surface=_default_parameters['free_surface'],
|
|
59
61
|
water_depth=None, sea_bottom=None,
|
|
60
62
|
omega=None, period=None, wavenumber=None, wavelength=None,
|
|
63
|
+
forward_speed=_default_parameters['forward_speed'],
|
|
61
64
|
rho=_default_parameters['rho'],
|
|
62
65
|
g=_default_parameters['g'],
|
|
66
|
+
wave_direction=_default_parameters['wave_direction'],
|
|
63
67
|
boundary_condition=None):
|
|
64
68
|
|
|
65
69
|
self.body = body
|
|
66
70
|
self.free_surface = float(free_surface)
|
|
67
71
|
self.rho = float(rho)
|
|
68
72
|
self.g = float(g)
|
|
73
|
+
self.forward_speed = float(forward_speed)
|
|
74
|
+
self.wave_direction = float(wave_direction) # Required for (diffraction problem) and (radiation problems with forward speed).
|
|
69
75
|
|
|
70
76
|
self.boundary_condition = boundary_condition
|
|
71
77
|
|
|
72
78
|
self.water_depth = _get_water_depth(free_surface, water_depth, sea_bottom, default_water_depth=_default_parameters["water_depth"])
|
|
73
79
|
self.omega, self.period, self.wavenumber, self.wavelength, self.provided_freq_type = \
|
|
74
|
-
self._get_frequencies(omega, period, wavenumber, wavelength)
|
|
80
|
+
self._get_frequencies(omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength)
|
|
75
81
|
|
|
76
82
|
self._check_data()
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
if forward_speed != 0.0:
|
|
85
|
+
dopplered_omega = self.omega - self.wavenumber*self.forward_speed*np.cos(self.wave_direction)
|
|
86
|
+
self.encounter_omega, self.encounter_period, self.encounter_wavenumber, self.encounter_wavelength, _ = \
|
|
87
|
+
self._get_frequencies(omega=abs(dopplered_omega))
|
|
88
|
+
|
|
89
|
+
if dopplered_omega >= 0.0:
|
|
90
|
+
self.encounter_wave_direction = self.wave_direction
|
|
91
|
+
else:
|
|
92
|
+
self.encounter_wave_direction = self.wave_direction + np.pi
|
|
93
|
+
else:
|
|
94
|
+
self.encounter_omega = self.omega
|
|
95
|
+
self.encounter_period = self.period
|
|
96
|
+
self.encounter_wavenumber = self.wavenumber
|
|
97
|
+
self.encounter_wavelength = self.wavelength
|
|
98
|
+
self.encounter_wave_direction = self.wave_direction
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _get_frequencies(self, *, omega=None, period=None, wavenumber=None, wavelength=None):
|
|
79
102
|
frequency_data = dict(omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength)
|
|
80
103
|
nb_provided_frequency_data = 4 - list(frequency_data.values()).count(None)
|
|
81
104
|
|
|
@@ -87,87 +110,115 @@ class LinearPotentialFlowProblem:
|
|
|
87
110
|
provided_freq_type = 'omega'
|
|
88
111
|
frequency_data = {'omega': _default_parameters['omega']}
|
|
89
112
|
else:
|
|
90
|
-
provided_freq_type = [k for k, v in frequency_data.items() if v is not None][0]
|
|
113
|
+
provided_freq_type = [k for (k, v) in frequency_data.items() if v is not None][0]
|
|
114
|
+
|
|
115
|
+
if ((float(frequency_data[provided_freq_type]) == 0.0 and provided_freq_type in {'omega', 'wavenumber'})
|
|
116
|
+
or (float(frequency_data[provided_freq_type]) == np.inf and provided_freq_type in {'period', 'wavelength'})):
|
|
117
|
+
omega = SymbolicMultiplication("0")
|
|
118
|
+
wavenumber = SymbolicMultiplication("0")
|
|
119
|
+
period = SymbolicMultiplication("∞")
|
|
120
|
+
wavelength = SymbolicMultiplication("∞")
|
|
121
|
+
elif ((float(frequency_data[provided_freq_type]) == 0.0 and provided_freq_type in {'period', 'wavelength'})
|
|
122
|
+
or (float(frequency_data[provided_freq_type]) == np.inf and provided_freq_type in {'omega', 'wavenumber'})):
|
|
123
|
+
omega = SymbolicMultiplication("∞")
|
|
124
|
+
wavenumber = SymbolicMultiplication("∞")
|
|
125
|
+
period = SymbolicMultiplication("0")
|
|
126
|
+
wavelength = SymbolicMultiplication("0")
|
|
127
|
+
else:
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
if provided_freq_type in {'omega', 'period'}:
|
|
130
|
+
if provided_freq_type == 'omega':
|
|
131
|
+
omega = frequency_data['omega']
|
|
132
|
+
period = 2*np.pi/omega
|
|
133
|
+
else: # provided_freq_type is 'period'
|
|
134
|
+
period = frequency_data['period']
|
|
135
|
+
omega = 2*np.pi/period
|
|
136
|
+
|
|
137
|
+
if self.water_depth == np.inf:
|
|
138
|
+
wavenumber = omega**2/self.g
|
|
139
|
+
else:
|
|
140
|
+
wavenumber = newton(lambda k: k*np.tanh(k*self.water_depth) - omega**2/self.g, x0=1.0)
|
|
141
|
+
wavelength = 2*np.pi/wavenumber
|
|
94
142
|
|
|
143
|
+
else: # provided_freq_type is 'wavelength' or 'wavenumber'
|
|
144
|
+
if provided_freq_type == 'wavelength':
|
|
145
|
+
wavelength = frequency_data['wavelength']
|
|
146
|
+
wavenumber = 2*np.pi/wavelength
|
|
147
|
+
else: # provided_freq_type is 'wavenumber'
|
|
148
|
+
wavenumber = frequency_data['wavenumber']
|
|
149
|
+
wavelength = 2*np.pi/wavenumber
|
|
95
150
|
|
|
96
|
-
|
|
97
|
-
if provided_freq_type == 'omega':
|
|
98
|
-
omega = frequency_data['omega']
|
|
151
|
+
omega = np.sqrt(self.g*wavenumber*np.tanh(wavenumber*self.water_depth))
|
|
99
152
|
period = 2*np.pi/omega
|
|
100
|
-
else: # provided_freq_type is 'period'
|
|
101
|
-
period = frequency_data['period']
|
|
102
|
-
omega = 2*np.pi/period
|
|
103
|
-
|
|
104
|
-
if self.water_depth == np.infty:
|
|
105
|
-
wavenumber = omega**2/self.g
|
|
106
|
-
else:
|
|
107
|
-
wavenumber = newton(lambda k: k*np.tanh(k*self.water_depth) - omega**2/self.g, x0=1.0)
|
|
108
|
-
wavelength = 2*np.pi/wavenumber
|
|
109
|
-
|
|
110
|
-
else: # provided_freq_type is 'wavelength' or 'wavenumber'
|
|
111
|
-
if provided_freq_type == 'wavelength':
|
|
112
|
-
wavelength = frequency_data['wavelength']
|
|
113
|
-
wavenumber = 2*np.pi/wavelength
|
|
114
|
-
else: # provided_freq_type is 'wavenumber'
|
|
115
|
-
wavenumber = frequency_data['wavenumber']
|
|
116
|
-
wavelength = 2*np.pi/wavenumber
|
|
117
|
-
|
|
118
|
-
omega = np.sqrt(self.g*wavenumber*np.tanh(wavenumber*self.water_depth))
|
|
119
|
-
period = 2*np.pi/omega
|
|
120
153
|
|
|
121
154
|
return omega, period, wavenumber, wavelength, provided_freq_type
|
|
122
155
|
|
|
123
156
|
def _check_data(self):
|
|
124
157
|
"""Sanity checks on the data."""
|
|
125
158
|
|
|
126
|
-
if self.free_surface not in {0.0, np.
|
|
159
|
+
if self.free_surface not in {0.0, np.inf}:
|
|
127
160
|
raise NotImplementedError(
|
|
128
161
|
f"Free surface is {self.free_surface}. "
|
|
129
162
|
"Only z=0 and z=∞ are accepted values for the free surface position."
|
|
130
163
|
)
|
|
131
164
|
|
|
132
|
-
if
|
|
165
|
+
if not (-2*np.pi-1e-3 <= self.wave_direction <= 2*np.pi+1e-3):
|
|
166
|
+
LOG.warning(f"The value {self.wave_direction} has been provided for the wave direction, and it does not look like an angle in radians. "
|
|
167
|
+
"The wave direction in Capytaine is defined in radians and not in degrees, so the result might not be what you expect. "
|
|
168
|
+
"If you were actually giving an angle in radians, use the modulo operator to give a value between -2π and 2π to disable this warning.")
|
|
169
|
+
|
|
170
|
+
if self.free_surface == np.inf and self.water_depth != np.inf:
|
|
171
|
+
|
|
133
172
|
raise NotImplementedError(
|
|
134
173
|
"Problems with a sea bottom but no free surface have not been implemented."
|
|
135
174
|
)
|
|
136
175
|
|
|
137
176
|
if self.water_depth < 0.0:
|
|
138
|
-
raise ValueError("`water_depth` should be
|
|
177
|
+
raise ValueError("`water_depth` should be strictly positive (provided water depth: {self.water_depth}).")
|
|
178
|
+
|
|
179
|
+
if float(self.omega) in {0, np.inf}:
|
|
180
|
+
if self.water_depth != np.inf:
|
|
181
|
+
LOG.warning(
|
|
182
|
+
f"Default Green function allows for {self.provided_freq_type}={float(self.__getattribute__(self.provided_freq_type))} only for infinite depth (provided water depth: {self.water_depth})."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if self.forward_speed != 0.0:
|
|
186
|
+
raise NotImplementedError(
|
|
187
|
+
f"omega={float(self.omega)} is only implemented without forward speed (provided forward speed: {self.forward_speed})."
|
|
188
|
+
)
|
|
139
189
|
|
|
140
|
-
if self.omega in {0, np.infty} and self.water_depth != np.infty:
|
|
141
|
-
raise NotImplementedError(
|
|
142
|
-
f"omega={self.omega} is only implemented for infinite depth."
|
|
143
|
-
)
|
|
144
190
|
|
|
145
191
|
if self.body is not None:
|
|
146
192
|
if ((isinstance(self.body.mesh, CollectionOfMeshes) and len(self.body.mesh) == 0)
|
|
147
193
|
or len(self.body.mesh.faces) == 0):
|
|
148
|
-
raise ValueError(f"The mesh of the body {self.body.
|
|
194
|
+
raise ValueError(f"The mesh of the body {self.body.__short_str__()} is empty.")
|
|
149
195
|
|
|
150
|
-
|
|
151
|
-
|
|
196
|
+
panels_above_fs = self.body.mesh.faces_centers[:, 2] >= self.free_surface + 1e-8
|
|
197
|
+
panels_below_sb = self.body.mesh.faces_centers[:, 2] <= -self.water_depth
|
|
198
|
+
if (any(panels_above_fs) or any(panels_below_sb)):
|
|
199
|
+
|
|
200
|
+
if not any(panels_below_sb):
|
|
201
|
+
issue = f"{np.count_nonzero(panels_above_fs)} panels above the free surface"
|
|
202
|
+
elif not any(panels_above_fs):
|
|
203
|
+
issue = f"{np.count_nonzero(panels_below_sb)} panels below the sea bottom"
|
|
204
|
+
else:
|
|
205
|
+
issue = (f"{np.count_nonzero(panels_above_fs)} panels above the free surface " +
|
|
206
|
+
f"and {np.count_nonzero(panels_below_sb)} panels below the sea bottom")
|
|
152
207
|
|
|
153
208
|
LOG.warning(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
209
|
+
f"The mesh of the body {self.body.__short_str__()} has {issue}.\n" +
|
|
210
|
+
"It has been clipped to fit inside the domain.\n" +
|
|
211
|
+
"To remove this warning, clip the mesh manually with the `immersed_part()` method."
|
|
157
212
|
)
|
|
158
213
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
f"The resolution of the mesh '{self.body.mesh.name}' of the body '{self.body.name}' "
|
|
162
|
-
f"might be insufficient for the wavelength λ={self.wavelength:.2e}.\n"
|
|
163
|
-
f"This warning appears because the largest panel of this mesh has radius {self.body.mesh.faces_radiuses.max():.2e} > λ/8."
|
|
164
|
-
)
|
|
214
|
+
self.body = self.body.immersed_part(free_surface=self.free_surface,
|
|
215
|
+
water_depth=self.water_depth)
|
|
165
216
|
|
|
166
217
|
if self.boundary_condition is not None:
|
|
167
218
|
if len(self.boundary_condition.shape) != 1:
|
|
168
|
-
raise ValueError("Expected a 1-dimensional array as boundary_condition")
|
|
219
|
+
raise ValueError(f"Expected a 1-dimensional array as boundary_condition. Provided boundary condition's shape: {self.boundary_condition.shape}.")
|
|
169
220
|
|
|
170
|
-
if self.boundary_condition.shape[0] != self.body.
|
|
221
|
+
if self.boundary_condition.shape[0] != self.body.mesh_including_lid.nb_faces:
|
|
171
222
|
raise ValueError(
|
|
172
223
|
f"The shape of the boundary condition ({self.boundary_condition.shape})"
|
|
173
224
|
f"does not match the number of faces of the mesh ({self.body.mesh.nb_faces})."
|
|
@@ -180,10 +231,14 @@ class LinearPotentialFlowProblem:
|
|
|
180
231
|
def _asdict(self):
|
|
181
232
|
return {"body_name": self.body_name,
|
|
182
233
|
"water_depth": self.water_depth,
|
|
183
|
-
"omega": self.omega,
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
234
|
+
"omega": float(self.omega),
|
|
235
|
+
"encounter_omega": float(self.encounter_omega),
|
|
236
|
+
"period": float(self.period),
|
|
237
|
+
"wavelength": float(self.wavelength),
|
|
238
|
+
"wavenumber": float(self.wavenumber),
|
|
239
|
+
"forward_speed": self.forward_speed,
|
|
240
|
+
"wave_direction": self.wave_direction,
|
|
241
|
+
"encounter_wave_direction": self.encounter_wave_direction,
|
|
187
242
|
"rho": self.rho,
|
|
188
243
|
"g": self.g}
|
|
189
244
|
|
|
@@ -200,9 +255,13 @@ class LinearPotentialFlowProblem:
|
|
|
200
255
|
|
|
201
256
|
def __str__(self):
|
|
202
257
|
"""Do not display default values in str(problem)."""
|
|
203
|
-
parameters = [f"body={self.
|
|
204
|
-
f"{self.provided_freq_type}={self.__getattribute__(self.provided_freq_type):.3f}",
|
|
258
|
+
parameters = [f"body={self.body.__short_str__() if self.body is not None else None}",
|
|
259
|
+
f"{self.provided_freq_type}={float(self.__getattribute__(self.provided_freq_type)):.3f}",
|
|
205
260
|
f"water_depth={self.water_depth}"]
|
|
261
|
+
|
|
262
|
+
if not self.forward_speed == _default_parameters['forward_speed']:
|
|
263
|
+
parameters.append(f"forward_speed={self.forward_speed:.3f}")
|
|
264
|
+
|
|
206
265
|
try:
|
|
207
266
|
parameters.extend(self._str_other_attributes())
|
|
208
267
|
except AttributeError:
|
|
@@ -223,10 +282,21 @@ class LinearPotentialFlowProblem:
|
|
|
223
282
|
def _repr_pretty_(self, p, cycle):
|
|
224
283
|
p.text(self.__str__())
|
|
225
284
|
|
|
285
|
+
def __rich_repr__(self):
|
|
286
|
+
yield "body", self.body, None
|
|
287
|
+
yield self.provided_freq_type, self.__getattribute__(self.provided_freq_type)
|
|
288
|
+
yield "water_depth", self.water_depth, _default_parameters["water_depth"]
|
|
289
|
+
try:
|
|
290
|
+
yield from self._specific_rich_repr()
|
|
291
|
+
except:
|
|
292
|
+
pass
|
|
293
|
+
yield "g", self.g, _default_parameters["g"]
|
|
294
|
+
yield "rho", self.rho, _default_parameters["rho"]
|
|
295
|
+
|
|
226
296
|
def _astuple(self):
|
|
227
297
|
return (self.body, self.free_surface, self.water_depth,
|
|
228
|
-
self.omega, self.period, self.wavenumber, self.wavelength,
|
|
229
|
-
self.rho, self.g)
|
|
298
|
+
float(self.omega), float(self.period), float(self.wavenumber), float(self.wavelength),
|
|
299
|
+
self.forward_speed, self.rho, self.g)
|
|
230
300
|
|
|
231
301
|
def __eq__(self, other):
|
|
232
302
|
if isinstance(other, LinearPotentialFlowProblem):
|
|
@@ -265,19 +335,17 @@ class DiffractionProblem(LinearPotentialFlowProblem):
|
|
|
265
335
|
free_surface=_default_parameters['free_surface'],
|
|
266
336
|
water_depth=None, sea_bottom=None,
|
|
267
337
|
omega=None, period=None, wavenumber=None, wavelength=None,
|
|
338
|
+
forward_speed=_default_parameters['forward_speed'],
|
|
268
339
|
rho=_default_parameters['rho'],
|
|
269
340
|
g=_default_parameters['g'],
|
|
270
341
|
wave_direction=_default_parameters['wave_direction']):
|
|
271
342
|
|
|
272
|
-
self.wave_direction = float(wave_direction)
|
|
273
|
-
|
|
274
343
|
super().__init__(body=body, free_surface=free_surface, water_depth=water_depth, sea_bottom=sea_bottom,
|
|
275
|
-
omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength,
|
|
344
|
+
omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength, wave_direction=wave_direction,
|
|
345
|
+
forward_speed=forward_speed, rho=rho, g=g)
|
|
276
346
|
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
"The wave direction in Capytaine is defined in radians and not in degrees, so the result might not be what you expect. "
|
|
280
|
-
"If you were actually giving an angle in radians, use the modulo operator to give a value between -2π and 2π to disable this warning.")
|
|
347
|
+
if float(self.omega) in {0.0, np.inf}:
|
|
348
|
+
raise NotImplementedError("DiffractionProblem does not support zero or infinite frequency.")
|
|
281
349
|
|
|
282
350
|
if self.body is not None:
|
|
283
351
|
|
|
@@ -285,21 +353,21 @@ class DiffractionProblem(LinearPotentialFlowProblem):
|
|
|
285
353
|
airy_waves_velocity(self.body.mesh.faces_centers, self)
|
|
286
354
|
* self.body.mesh.faces_normals
|
|
287
355
|
).sum(axis=1)
|
|
356
|
+
# Note that even with forward speed, this is computed based on the
|
|
357
|
+
# frequency and not the encounter frequency.
|
|
358
|
+
|
|
359
|
+
if self.body.lid_mesh is not None:
|
|
360
|
+
self.boundary_condition = np.concatenate([self.boundary_condition, np.zeros(self.body.lid_mesh.nb_faces)])
|
|
288
361
|
|
|
289
362
|
if len(self.body.dofs) == 0:
|
|
290
363
|
LOG.warning(f"The body {self.body.name} used in diffraction problem has no dofs!")
|
|
291
364
|
|
|
292
|
-
def _astuple(self):
|
|
293
|
-
return super()._astuple() + (self.wave_direction,)
|
|
294
|
-
|
|
295
|
-
def _asdict(self):
|
|
296
|
-
d = super()._asdict()
|
|
297
|
-
d["wave_direction"] = self.wave_direction
|
|
298
|
-
return d
|
|
299
|
-
|
|
300
365
|
def _str_other_attributes(self):
|
|
301
366
|
return [f"wave_direction={self.wave_direction:.3f}"]
|
|
302
367
|
|
|
368
|
+
def _specific_rich_repr(self):
|
|
369
|
+
yield "wave_direction", self.wave_direction, _default_parameters["wave_direction"]
|
|
370
|
+
|
|
303
371
|
def make_results_container(self, *args, **kwargs):
|
|
304
372
|
return DiffractionResult(self, *args, **kwargs)
|
|
305
373
|
|
|
@@ -312,6 +380,8 @@ class RadiationProblem(LinearPotentialFlowProblem):
|
|
|
312
380
|
free_surface=_default_parameters['free_surface'],
|
|
313
381
|
water_depth=None, sea_bottom=None,
|
|
314
382
|
omega=None, period=None, wavenumber=None, wavelength=None,
|
|
383
|
+
forward_speed=_default_parameters['forward_speed'],
|
|
384
|
+
wave_direction=_default_parameters['wave_direction'],
|
|
315
385
|
rho=_default_parameters['rho'],
|
|
316
386
|
g=_default_parameters['g'],
|
|
317
387
|
radiating_dof=None):
|
|
@@ -319,7 +389,8 @@ class RadiationProblem(LinearPotentialFlowProblem):
|
|
|
319
389
|
self.radiating_dof = radiating_dof
|
|
320
390
|
|
|
321
391
|
super().__init__(body=body, free_surface=free_surface, water_depth=water_depth, sea_bottom=sea_bottom,
|
|
322
|
-
omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength,
|
|
392
|
+
omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength,
|
|
393
|
+
wave_direction=wave_direction, forward_speed=forward_speed, rho=rho, g=g)
|
|
323
394
|
|
|
324
395
|
if self.body is not None:
|
|
325
396
|
|
|
@@ -330,13 +401,32 @@ class RadiationProblem(LinearPotentialFlowProblem):
|
|
|
330
401
|
self.radiating_dof = next(iter(self.body.dofs))
|
|
331
402
|
|
|
332
403
|
if self.radiating_dof not in self.body.dofs:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
raise ValueError("Unrecognized degree of freedom name.")
|
|
404
|
+
raise ValueError(f"In {self}:\n"
|
|
405
|
+
f"the radiating dof {repr(self.radiating_dof)} is not one of the degrees of freedom of the body.\n"
|
|
406
|
+
f"The dofs of the body are {list(self.body.dofs.keys())}")
|
|
337
407
|
|
|
338
408
|
dof = self.body.dofs[self.radiating_dof]
|
|
339
|
-
|
|
409
|
+
|
|
410
|
+
self.boundary_condition = -1j * self.encounter_omega * np.sum(dof * self.body.mesh.faces_normals, axis=1)
|
|
411
|
+
|
|
412
|
+
if self.forward_speed != 0.0:
|
|
413
|
+
if self.radiating_dof.lower() == "pitch":
|
|
414
|
+
ddofdx_dot_n = np.array([nz for (nx, ny, nz) in self.body.mesh.faces_normals])
|
|
415
|
+
elif self.radiating_dof.lower() == "yaw":
|
|
416
|
+
ddofdx_dot_n = np.array([-ny for (nx, ny, nz) in self.body.mesh.faces_normals])
|
|
417
|
+
elif self.radiating_dof.lower() in {"surge", "sway", "heave", "roll"}:
|
|
418
|
+
ddofdx_dot_n = 0.0
|
|
419
|
+
else:
|
|
420
|
+
raise NotImplementedError(
|
|
421
|
+
"Radiation problem with forward speed is currently only implemented for a single rigid body.\n"
|
|
422
|
+
"Only radiating dofs with name in {'Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw'} are supported.\n"
|
|
423
|
+
f"Got instead `radiating_dof={self.radiating_dof}`"
|
|
424
|
+
)
|
|
425
|
+
self.boundary_condition += self.forward_speed * ddofdx_dot_n
|
|
426
|
+
|
|
427
|
+
if self.body.lid_mesh is not None:
|
|
428
|
+
self.boundary_condition = np.concatenate([self.boundary_condition, np.zeros(self.body.lid_mesh.nb_faces)])
|
|
429
|
+
|
|
340
430
|
|
|
341
431
|
def _astuple(self):
|
|
342
432
|
return super()._astuple() + (self.radiating_dof,)
|
|
@@ -347,7 +437,13 @@ class RadiationProblem(LinearPotentialFlowProblem):
|
|
|
347
437
|
return d
|
|
348
438
|
|
|
349
439
|
def _str_other_attributes(self):
|
|
350
|
-
|
|
440
|
+
if self.forward_speed != 0.0:
|
|
441
|
+
return [f"wave_direction={self.wave_direction:.3f}, radiating_dof=\'{self.radiating_dof}\'"]
|
|
442
|
+
else:
|
|
443
|
+
return [f"radiating_dof=\'{self.radiating_dof}\'"]
|
|
444
|
+
|
|
445
|
+
def _specific_rich_repr(self):
|
|
446
|
+
yield "radiating_dof", self.radiating_dof
|
|
351
447
|
|
|
352
448
|
def make_results_container(self, *args, **kwargs):
|
|
353
449
|
return RadiationResult(self, *args, **kwargs)
|
|
@@ -358,48 +454,54 @@ class LinearPotentialFlowResult:
|
|
|
358
454
|
def __init__(self, problem, forces=None, sources=None, potential=None, pressure=None):
|
|
359
455
|
self.problem = problem
|
|
360
456
|
|
|
457
|
+
self.forces = forces if forces is not None else {}
|
|
361
458
|
self.sources = sources
|
|
362
459
|
self.potential = potential
|
|
363
460
|
self.pressure = pressure
|
|
364
|
-
|
|
461
|
+
|
|
462
|
+
self.fs_elevation = {} # Only used in legacy `get_free_surface_elevation`. To be removed?
|
|
365
463
|
|
|
366
464
|
# Copy data from problem
|
|
367
465
|
self.body = self.problem.body
|
|
368
466
|
self.free_surface = self.problem.free_surface
|
|
369
467
|
self.omega = self.problem.omega
|
|
468
|
+
self.period = self.problem.period
|
|
469
|
+
self.wavenumber = self.problem.wavenumber
|
|
470
|
+
self.wavelength = self.problem.wavelength
|
|
471
|
+
self.forward_speed = self.problem.forward_speed
|
|
472
|
+
self.wave_direction = self.problem.wave_direction
|
|
473
|
+
self.encounter_omega = self.problem.encounter_omega
|
|
474
|
+
self.encounter_period = self.problem.encounter_period
|
|
475
|
+
self.encounter_wavenumber = self.problem.encounter_wavenumber
|
|
476
|
+
self.encounter_wavelength = self.problem.encounter_wavelength
|
|
477
|
+
self.encounter_wave_direction = self.problem.encounter_wave_direction
|
|
370
478
|
self.rho = self.problem.rho
|
|
371
479
|
self.g = self.problem.g
|
|
372
480
|
self.boundary_condition = self.problem.boundary_condition
|
|
373
481
|
self.water_depth = self.problem.water_depth
|
|
374
482
|
self.depth = self.problem.water_depth
|
|
375
|
-
self.wavenumber = self.problem.wavenumber
|
|
376
|
-
self.wavelength = self.problem.wavelength
|
|
377
|
-
self.period = self.problem.period
|
|
378
483
|
self.provided_freq_type = self.problem.provided_freq_type
|
|
379
484
|
self.body_name = self.problem.body_name
|
|
380
485
|
self.influenced_dofs = self.problem.influenced_dofs
|
|
381
486
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
def store_force(self, dof, force):
|
|
387
|
-
pass # Implemented in sub-classes
|
|
487
|
+
@property
|
|
488
|
+
def force(self):
|
|
489
|
+
# Just an alias
|
|
490
|
+
return self.forces
|
|
388
491
|
|
|
389
492
|
__str__ = LinearPotentialFlowProblem.__str__
|
|
390
493
|
__repr__ = LinearPotentialFlowProblem.__repr__
|
|
391
494
|
_repr_pretty_ = LinearPotentialFlowProblem._repr_pretty_
|
|
495
|
+
__rich_repr__ = LinearPotentialFlowProblem.__rich_repr__
|
|
392
496
|
|
|
393
497
|
|
|
394
498
|
class DiffractionResult(LinearPotentialFlowResult):
|
|
395
499
|
|
|
396
500
|
def __init__(self, problem, *args, **kwargs):
|
|
397
|
-
self.forces = {}
|
|
398
501
|
super().__init__(problem, *args, **kwargs)
|
|
399
|
-
self.wave_direction = self.problem.wave_direction
|
|
400
502
|
|
|
401
|
-
|
|
402
|
-
|
|
503
|
+
_str_other_attributes = DiffractionProblem._str_other_attributes
|
|
504
|
+
_specific_rich_repr = DiffractionProblem._specific_rich_repr
|
|
403
505
|
|
|
404
506
|
@property
|
|
405
507
|
def records(self):
|
|
@@ -415,20 +517,29 @@ class DiffractionResult(LinearPotentialFlowResult):
|
|
|
415
517
|
class RadiationResult(LinearPotentialFlowResult):
|
|
416
518
|
|
|
417
519
|
def __init__(self, problem, *args, **kwargs):
|
|
418
|
-
self.added_masses = {}
|
|
419
|
-
self.radiation_dampings = {}
|
|
420
520
|
super().__init__(problem, *args, **kwargs)
|
|
421
521
|
self.radiating_dof = self.problem.radiating_dof
|
|
422
522
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
523
|
+
_str_other_attributes = RadiationProblem._str_other_attributes
|
|
524
|
+
_specific_rich_repr = RadiationProblem._specific_rich_repr
|
|
525
|
+
|
|
526
|
+
@property
|
|
527
|
+
def added_mass(self):
|
|
528
|
+
return {dof: float(np.real(force)/(self.encounter_omega*self.encounter_omega)) for (dof, force) in self.forces.items()}
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def radiation_damping(self):
|
|
532
|
+
return {dof: float(np.imag(force)/self.encounter_omega) for (dof, force) in self.forces.items()}
|
|
533
|
+
|
|
534
|
+
# Aliases for backward compatibility
|
|
535
|
+
added_masses = added_mass
|
|
536
|
+
radiation_dampings = radiation_damping
|
|
426
537
|
|
|
427
538
|
@property
|
|
428
539
|
def records(self):
|
|
429
540
|
params = self.problem._asdict()
|
|
430
541
|
return [dict(params,
|
|
431
542
|
influenced_dof=dof,
|
|
432
|
-
added_mass=self.
|
|
433
|
-
radiation_damping=self.
|
|
543
|
+
added_mass=self.added_mass[dof],
|
|
544
|
+
radiation_damping=self.radiation_damping[dof])
|
|
434
545
|
for dof in self.influenced_dofs]
|