capytaine 2.3.1__cp310-cp310-win_amd64.whl → 3.0.0a1__cp310-cp310-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.
Files changed (88) hide show
  1. capytaine/__about__.py +7 -2
  2. capytaine/__init__.py +11 -15
  3. capytaine/bem/engines.py +234 -354
  4. capytaine/bem/problems_and_results.py +14 -13
  5. capytaine/bem/solver.py +204 -80
  6. capytaine/bodies/bodies.py +278 -869
  7. capytaine/bodies/dofs.py +136 -9
  8. capytaine/bodies/hydrostatics.py +540 -0
  9. capytaine/bodies/multibodies.py +216 -0
  10. capytaine/green_functions/{libs/Delhommeau_float32.cp310-win_amd64.dll.a → Delhommeau_float32.cp310-win_amd64.dll.a} +0 -0
  11. capytaine/green_functions/Delhommeau_float32.cp310-win_amd64.pyd +0 -0
  12. capytaine/green_functions/{libs/Delhommeau_float64.cp310-win_amd64.dll.a → Delhommeau_float64.cp310-win_amd64.dll.a} +0 -0
  13. capytaine/green_functions/Delhommeau_float64.cp310-win_amd64.pyd +0 -0
  14. capytaine/green_functions/abstract_green_function.py +2 -2
  15. capytaine/green_functions/delhommeau.py +31 -16
  16. capytaine/green_functions/hams.py +19 -13
  17. capytaine/io/legacy.py +3 -103
  18. capytaine/io/xarray.py +11 -6
  19. capytaine/meshes/__init__.py +2 -6
  20. capytaine/meshes/abstract_meshes.py +375 -0
  21. capytaine/meshes/clean.py +302 -0
  22. capytaine/meshes/clip.py +347 -0
  23. capytaine/meshes/export.py +89 -0
  24. capytaine/meshes/geometry.py +244 -394
  25. capytaine/meshes/io.py +433 -0
  26. capytaine/meshes/meshes.py +617 -681
  27. capytaine/meshes/predefined/cylinders.py +22 -56
  28. capytaine/meshes/predefined/rectangles.py +26 -85
  29. capytaine/meshes/predefined/spheres.py +4 -11
  30. capytaine/meshes/quality.py +118 -407
  31. capytaine/meshes/surface_integrals.py +48 -29
  32. capytaine/meshes/symmetric_meshes.py +641 -0
  33. capytaine/meshes/visualization.py +353 -0
  34. capytaine/post_pro/free_surfaces.py +1 -4
  35. capytaine/post_pro/kochin.py +10 -10
  36. capytaine/tools/block_circulant_matrices.py +275 -0
  37. capytaine/tools/lists_of_points.py +2 -2
  38. capytaine/tools/memory_monitor.py +45 -0
  39. capytaine/tools/symbolic_multiplication.py +13 -1
  40. capytaine/tools/timer.py +58 -34
  41. capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
  42. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
  43. capytaine-3.0.0a1.dist-info/RECORD +70 -0
  44. capytaine/bodies/predefined/__init__.py +0 -6
  45. capytaine/bodies/predefined/cylinders.py +0 -151
  46. capytaine/bodies/predefined/rectangles.py +0 -111
  47. capytaine/bodies/predefined/spheres.py +0 -70
  48. capytaine/green_functions/FinGreen3D/.gitignore +0 -1
  49. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
  50. capytaine/green_functions/FinGreen3D/LICENSE +0 -165
  51. capytaine/green_functions/FinGreen3D/Makefile +0 -16
  52. capytaine/green_functions/FinGreen3D/README.md +0 -24
  53. capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
  54. capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
  55. capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
  56. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
  57. capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
  58. capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
  59. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
  60. capytaine/green_functions/libs/Delhommeau_float32.cp310-win_amd64.pyd +0 -0
  61. capytaine/green_functions/libs/Delhommeau_float64.cp310-win_amd64.pyd +0 -0
  62. capytaine/green_functions/libs/__init__.py +0 -0
  63. capytaine/io/mesh_loaders.py +0 -1086
  64. capytaine/io/mesh_writers.py +0 -692
  65. capytaine/io/meshio.py +0 -38
  66. capytaine/matrices/__init__.py +0 -16
  67. capytaine/matrices/block.py +0 -592
  68. capytaine/matrices/block_toeplitz.py +0 -325
  69. capytaine/matrices/builders.py +0 -89
  70. capytaine/matrices/linear_solvers.py +0 -232
  71. capytaine/matrices/low_rank.py +0 -395
  72. capytaine/meshes/clipper.py +0 -465
  73. capytaine/meshes/collections.py +0 -342
  74. capytaine/meshes/mesh_like_protocol.py +0 -37
  75. capytaine/meshes/properties.py +0 -276
  76. capytaine/meshes/quadratures.py +0 -80
  77. capytaine/meshes/symmetric.py +0 -462
  78. capytaine/tools/lru_cache.py +0 -49
  79. capytaine/ui/vtk/__init__.py +0 -3
  80. capytaine/ui/vtk/animation.py +0 -329
  81. capytaine/ui/vtk/body_viewer.py +0 -28
  82. capytaine/ui/vtk/helpers.py +0 -82
  83. capytaine/ui/vtk/mesh_viewer.py +0 -461
  84. capytaine-2.3.1.dist-info/DELVEWHEEL +0 -2
  85. capytaine-2.3.1.dist-info/RECORD +0 -97
  86. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
  87. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
  88. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,216 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from itertools import chain, accumulate
5
+ from typing import Union, List, Optional
6
+ from functools import cached_property, lru_cache
7
+
8
+ import numpy as np
9
+ import xarray as xr
10
+
11
+ from capytaine.bodies.dofs import (
12
+ AbstractDof,
13
+ DofOnSubmesh,
14
+ add_dofs_labels_to_vector,
15
+ add_dofs_labels_to_matrix
16
+ )
17
+
18
+ LOG = logging.getLogger(__name__)
19
+
20
+
21
+ class Multibody:
22
+ def __init__(
23
+ self,
24
+ bodies: List[Union[FloatingBody, Multibody]],
25
+ # own_dofs: Optional[Dict[str, np.array]] = None,
26
+ *,
27
+ name: Optional[str] = None
28
+ ):
29
+ self.bodies = bodies
30
+
31
+ if len(set(b.name for b in self.bodies)) < len(self.bodies):
32
+ raise ValueError(
33
+ "In Multibody, all component bodies must have a distinct name.\n"
34
+ f"Got: {[b.name for b in self.bodies]}"
35
+ )
36
+
37
+ # if own_dofs is None:
38
+ # self.own_dofs = {}
39
+ # else:
40
+ # self.own_dofs = own_dofs
41
+
42
+ if name is None:
43
+ self.name = '+'.join(b.name for b in self.bodies)
44
+ else:
45
+ self.name = name
46
+
47
+ # Keep legacy behavior of former mesh joining
48
+ for matrix_name in ["inertia_matrix", "hydrostatic_stiffness"]:
49
+ if all(hasattr(body, matrix_name) for body in bodies):
50
+ from scipy.linalg import block_diag
51
+ setattr(self, matrix_name, self.add_dofs_labels_to_matrix(
52
+ block_diag(*[getattr(body, matrix_name) for body in bodies])
53
+ ))
54
+
55
+ LOG.debug(f"New multibody: {self.__str__()}.")
56
+
57
+ @lru_cache
58
+ def as_FloatingBody(self):
59
+ from capytaine.bodies.bodies import FloatingBody
60
+ if all(body.mass is not None for body in self.bodies):
61
+ total_mass = sum(body.mass for body in self.bodies)
62
+ else:
63
+ total_mass = None
64
+
65
+ if (all(body.mass is not None for body in self.bodies)
66
+ and all(body.center_of_mass is not None for body in self.bodies)):
67
+ new_cog = sum(body.mass*np.asarray(body.center_of_mass) for body in self.bodies)/total_mass
68
+ else:
69
+ new_cog = None
70
+
71
+ return FloatingBody(
72
+ mesh=self.mesh,
73
+ dofs=self.dofs,
74
+ lid_mesh=self.lid_mesh,
75
+ mass=total_mass,
76
+ center_of_mass=new_cog,
77
+ name=self.name,
78
+ )
79
+
80
+ def __str__(self):
81
+ short_bodies = ', '.join(b.__short_str__() for b in self.bodies)
82
+ # short_dofs = '{' + ', '.join('"{}": ...'.format(d) for d in self.own_dofs) + '}'
83
+ return f"Multibody({short_bodies})" #, own_dofs={short_dofs})"
84
+
85
+ def __short_str__(self):
86
+ return str(self)
87
+
88
+ def __repr__(self):
89
+ return str(self)
90
+
91
+ def _check_dofs_shape_consistency(self):
92
+ # TODO
93
+ ...
94
+
95
+ @cached_property
96
+ def minimal_computable_wavelength(self):
97
+ return min(b.minimal_computable_wavelength for b in self.bodies)
98
+
99
+ def first_irregular_frequency_estimate(self, *args, **kwargs):
100
+ return min(b.first_irregular_frequency_estimate(*args, **kwargs) for b in self.bodies)
101
+
102
+ @cached_property
103
+ def mesh(self):
104
+ return self.bodies[0].mesh.join_meshes(*[b.mesh for b in self.bodies[1:]])
105
+
106
+ @cached_property
107
+ def lid_mesh(self):
108
+ if all(body.lid_mesh is None for body in self.bodies):
109
+ return None
110
+ else:
111
+ lid_meshes = [body.lid_mesh.copy() for body in self.bodies if body.lid_mesh is not None]
112
+ joined_lid = lid_meshes[0].join_meshes(*lid_meshes[1:], name=f"{self.name}_lid_mesh")
113
+ return joined_lid
114
+
115
+ @cached_property
116
+ def mesh_including_lid(self):
117
+ return self.bodies[0].mesh_including_lid.join_meshes(*[b.mesh_including_lid for b in self.bodies[1:]])
118
+
119
+ @cached_property
120
+ def hull_mask(self):
121
+ return np.concatenate([b.hull_mask for b in self.bodies])
122
+
123
+ @property
124
+ def nb_dofs(self):
125
+ return sum(b.nb_dofs for b in self.bodies) # + len(self.own_dofs)
126
+
127
+ @cached_property
128
+ def dofs(self):
129
+ for body in self.bodies:
130
+ body._check_dofs_shape_consistency()
131
+
132
+ componenents_dofs = {}
133
+ cum_nb_faces = accumulate(chain([0], (body.mesh.nb_faces for body in self.bodies)))
134
+ total_nb_faces = sum(body.mesh.nb_faces for body in self.bodies)
135
+ for body, nbf in zip(self.bodies, cum_nb_faces):
136
+ # nbf is the cumulative number of faces of the previous subbodies,
137
+ # that is the offset of the indices of the faces of the current body.
138
+ for name, dof in body.dofs.items():
139
+ if isinstance(dof, AbstractDof):
140
+ new_dof = DofOnSubmesh(dof, range(nbf, nbf+body.mesh.nb_faces))
141
+ else:
142
+ new_dof = np.zeros((total_nb_faces, 3))
143
+ new_dof[nbf:nbf+len(dof), :] = dof
144
+
145
+ if '__' not in name:
146
+ new_dof_name = '__'.join([body.name, name])
147
+ else:
148
+ # The body is probably a combination of bodies already.
149
+ # So for the associativity of the + operation,
150
+ # it is better to keep the same name.
151
+ new_dof_name = name
152
+ componenents_dofs[new_dof_name] = new_dof
153
+
154
+ return {**componenents_dofs} #, **self.own_dofs}
155
+
156
+ def add_dofs_labels_to_vector(self, vector):
157
+ """Helper function turning a bare vector into a vector labelled by the name of the dofs of the body,
158
+ to be used for instance for the computation of RAO."""
159
+ return add_dofs_labels_to_vector(self.dofs.keys(), vector)
160
+
161
+ def add_dofs_labels_to_matrix(self, matrix):
162
+ """Helper function turning a bare matrix into a matrix labelled by the name of the dofs of the body,
163
+ to be used for instance for the computation of RAO."""
164
+ return add_dofs_labels_to_matrix(self.dofs.keys(), matrix)
165
+
166
+ def immersed_part(self, *args, **kwargs):
167
+ return Multibody(
168
+ [b.immersed_part() for b in self.bodies],
169
+ # own_dofs=None, # TODO
170
+ )
171
+
172
+ def __add__(self, body_to_add):
173
+ return self.join_bodies(body_to_add)
174
+
175
+ def join_bodies(*bodies, name=None):
176
+ from capytaine.bodies.multibodies import Multibody
177
+ return Multibody(bodies, name=name)
178
+
179
+ def integrate_pressure(self, pressure):
180
+ return self.as_FloatingBody().integrate_pressure(pressure)
181
+
182
+ @cached_property
183
+ def center_of_buoyancy(self):
184
+ return {b.name: b.center_of_buoyancy for b in self.bodies}
185
+
186
+ @cached_property
187
+ def center_of_mass(self):
188
+ return {b.name: b.center_of_mass for b in self.bodies}
189
+
190
+ @cached_property
191
+ def volume(self):
192
+ return {b.name: b.volume for b in self.bodies}
193
+
194
+ @cached_property
195
+ def mass(self):
196
+ return {b.name: b.mass for b in self.bodies}
197
+
198
+ def _combine_component_matrices(self, matrices):
199
+ for m, b in zip(matrices, self.bodies):
200
+ m.coords['radiating_dof'] = np.array([b.name + '__' + k for k in m.coords['radiating_dof'].values])
201
+ m.coords['influenced_dof'] = np.array([b.name + '__' + k for k in m.coords['influenced_dof'].values])
202
+
203
+ return xr.concat(
204
+ matrices,
205
+ dim="radiating_dof",
206
+ fill_value=0.0
207
+ ).sel(
208
+ radiating_dof=list(self.dofs.keys()),
209
+ influenced_dof=list(self.dofs.keys())
210
+ )
211
+
212
+ def compute_hydrostatic_stiffness(self, *, rho=1000.0, g=9.81):
213
+ return self._combine_component_matrices([b.compute_hydrostatic_stiffness(rho=rho, g=g) for b in self.bodies])
214
+
215
+ def compute_rigid_body_inertia(self, rho=1000.0):
216
+ return self._combine_component_matrices([b.compute_rigid_body_inertia(rho=rho) for b in self.bodies])
@@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
6
6
 
7
7
  import numpy as np
8
8
 
9
- from capytaine.meshes.mesh_like_protocol import MeshLike
9
+ from capytaine.meshes.abstract_meshes import AbstractMesh
10
10
 
11
11
 
12
12
  class GreenFunctionEvaluationError(Exception):
@@ -19,7 +19,7 @@ class AbstractGreenFunction(ABC):
19
19
  floating_point_precision: str
20
20
 
21
21
  def _get_colocation_points_and_normals(self, mesh1, mesh2, adjoint_double_layer):
22
- if isinstance(mesh1, MeshLike):
22
+ if isinstance(mesh1, AbstractMesh):
23
23
  collocation_points = mesh1.faces_centers
24
24
  nb_collocation_points = mesh1.nb_faces
25
25
  if not adjoint_double_layer: # Computing the D matrix
@@ -116,7 +116,7 @@ class Delhommeau(AbstractGreenFunction):
116
116
  gf_singularities=_default_parameters["gf_singularities"],
117
117
  ):
118
118
 
119
- self.fortran_core = import_module(f"capytaine.green_functions.libs.Delhommeau_{floating_point_precision}")
119
+ self.fortran_core = import_module(f"capytaine.green_functions.Delhommeau_{floating_point_precision}")
120
120
 
121
121
  self.tabulation_grid_shape = tabulation_grid_shape
122
122
  fortran_enum = {
@@ -322,20 +322,22 @@ class Delhommeau(AbstractGreenFunction):
322
322
  else:
323
323
  raise ValueError(f"Unrecognized name for the Prony decomposition method: {repr(method)}. Expected 'python' or 'fortran'.")
324
324
 
325
- def evaluate_rankine_only(self,
326
- mesh1, mesh2,
327
- adjoint_double_layer=True, early_dot_product=True
328
- ):
325
+ def evaluate_rankine_only(
326
+ self,
327
+ mesh1, mesh2, *,
328
+ adjoint_double_layer=True, early_dot_product=True,
329
+ diagonal_term_in_double_layer=True,
330
+ ):
329
331
  r"""Construct the matrices between mesh1 (that can also be a list of points)
330
332
  and mesh2 for a Rankine kernel.
331
333
 
332
334
  Parameters
333
335
  ----------
334
- mesh1: Mesh or CollectionOfMeshes or list of points
336
+ mesh1: MeshLike or list of points
335
337
  mesh of the receiving body (where the potential is measured)
336
338
  if only S is wanted or early_dot_product is False, then only a list
337
339
  of points as an array of shape (n, 3) can be passed.
338
- mesh2: Mesh or CollectionOfMeshes
340
+ mesh2: MeshLike
339
341
  mesh of the source body (over which the source distribution is integrated)
340
342
  adjoint_double_layer: bool, optional
341
343
  compute double layer for direct method (F) or adjoint double layer
@@ -343,6 +345,11 @@ class Delhommeau(AbstractGreenFunction):
343
345
  early_dot_product: boolean, optional
344
346
  if False, return K as a (n, m, 3) array storing ∫∇G
345
347
  if True, return K as a (n, m) array storing ∫∇G·n
348
+ diagonal_term_in_double_layer: boolean, optional
349
+ if True, add the I/2 term in the double layer operator
350
+ It is assumed that mesh1 == mesh2, or at least that
351
+ the `n := min(mesh1.nb_faces, mesh2.nb_faces)` first faces
352
+ of each mesh are identical.
346
353
 
347
354
  Returns
348
355
  -------
@@ -365,9 +372,10 @@ class Delhommeau(AbstractGreenFunction):
365
372
  adjoint_double_layer,
366
373
  S, K)
367
374
 
368
- if mesh1 is mesh2:
375
+ if diagonal_term_in_double_layer:
376
+ n = min(K.shape[0], K.shape[1])
369
377
  self.fortran_core.matrices.add_diagonal_term(
370
- mesh2.faces_centers, early_dot_product_normals, np.inf, K,
378
+ mesh2.faces_centers[:n, :], early_dot_product_normals, np.inf, K,
371
379
  )
372
380
 
373
381
  S, K = np.real(S), np.real(K)
@@ -383,11 +391,12 @@ class Delhommeau(AbstractGreenFunction):
383
391
  return S, K
384
392
 
385
393
 
386
- def evaluate(self,
387
- mesh1, mesh2,
388
- free_surface=0.0, water_depth=np.inf, wavenumber=1.0,
389
- adjoint_double_layer=True, early_dot_product=True
390
- ):
394
+ def evaluate(
395
+ self, mesh1, mesh2, *,
396
+ free_surface=0.0, water_depth=np.inf, wavenumber=1.0,
397
+ adjoint_double_layer=True, early_dot_product=True,
398
+ diagonal_term_in_double_layer=True,
399
+ ):
391
400
  r"""The main method of the class, called by the engine to assemble the influence matrices.
392
401
 
393
402
  Parameters
@@ -408,6 +417,11 @@ class Delhommeau(AbstractGreenFunction):
408
417
  early_dot_product: boolean, optional
409
418
  if False, return K as a (n, m, 3) array storing ∫∇G
410
419
  if True, return K as a (n, m) array storing ∫∇G·n
420
+ diagonal_term_in_double_layer: boolean, optional
421
+ if True, add the I/2 term in the double layer operator.
422
+ It is assumed that mesh1 == mesh2, or at least that
423
+ the `n := min(mesh1.nb_faces, mesh2.nb_faces)` first faces
424
+ of each mesh are identical.
411
425
 
412
426
  Returns
413
427
  -------
@@ -483,9 +497,10 @@ class Delhommeau(AbstractGreenFunction):
483
497
  S, K
484
498
  )
485
499
 
486
- if mesh1 is mesh2:
500
+ if diagonal_term_in_double_layer:
501
+ n = min(K.shape[0], K.shape[1])
487
502
  self.fortran_core.matrices.add_diagonal_term(
488
- mesh2.faces_centers, early_dot_product_normals, free_surface, K,
503
+ mesh2.faces_centers[:n, :], early_dot_product_normals, free_surface, K,
489
504
  )
490
505
 
491
506
  if np.any(np.isnan(S)) or np.any(np.isnan(K)):
@@ -13,7 +13,7 @@ class LiangWuNoblesseGF(AbstractGreenFunction):
13
13
  """
14
14
  floating_point_precision = "float64"
15
15
 
16
- fortran_core = import_module("capytaine.green_functions.libs.Delhommeau_float64")
16
+ fortran_core = import_module("capytaine.green_functions.Delhommeau_float64")
17
17
  tabulation_grid_shape_index = fortran_core.constants.liang_wu_noblesse
18
18
  exportable_settings = {'green_function': "LiangWuNoblesseGF"}
19
19
 
@@ -36,11 +36,12 @@ class LiangWuNoblesseGF(AbstractGreenFunction):
36
36
  def _repr_pretty_(self, p, cycle):
37
37
  p.text(self.__repr__())
38
38
 
39
- def evaluate(self,
40
- mesh1, mesh2,
41
- free_surface=0.0, water_depth=np.inf, wavenumber=1.0,
42
- adjoint_double_layer=True, early_dot_product=True
43
- ):
39
+ def evaluate(
40
+ self, mesh1, mesh2, *,
41
+ free_surface=0.0, water_depth=np.inf, wavenumber,
42
+ adjoint_double_layer=True, early_dot_product=True,
43
+ diagonal_term_in_double_layer=True,
44
+ ):
44
45
 
45
46
  if free_surface == np.inf or water_depth < np.inf:
46
47
  raise NotImplementedError("LiangWuNoblesseGF() is only implemented for infinite depth with a free surface")
@@ -71,7 +72,7 @@ class LiangWuNoblesseGF(AbstractGreenFunction):
71
72
  S, K
72
73
  )
73
74
 
74
- if mesh1 is mesh2:
75
+ if diagonal_term_in_double_layer:
75
76
  self.fortran_core.matrices.add_diagonal_term(
76
77
  mesh2.faces_centers, early_dot_product_normals, free_surface, K,
77
78
  )
@@ -95,7 +96,7 @@ class FinGreen3D(AbstractGreenFunction):
95
96
  """
96
97
  floating_point_precision = "float64"
97
98
 
98
- fortran_core = import_module("capytaine.green_functions.libs.Delhommeau_float64")
99
+ fortran_core = import_module("capytaine.green_functions.Delhommeau_float64")
99
100
  finite_depth_method_index = fortran_core.constants.fingreen3d_method
100
101
  gf_singularities_index = fortran_core.constants.low_freq
101
102
 
@@ -129,7 +130,12 @@ class FinGreen3D(AbstractGreenFunction):
129
130
  return brentq(lambda y: omega2_h_over_g + y*np.tan(y), (2*i_root+1)*np.pi/2 + 1e-10, (2*i_root+2)*np.pi/2 - 1e-10)/depth
130
131
  return np.array([wavenumber] + [root(i_root) for i_root in range(nk-1)])
131
132
 
132
- def evaluate(self, mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer=True, early_dot_product=True):
133
+ def evaluate(
134
+ self, mesh1, mesh2, *,
135
+ free_surface=0.0, water_depth=np.inf, wavenumber,
136
+ adjoint_double_layer=True, early_dot_product=True,
137
+ diagonal_term_in_double_layer=True,
138
+ ):
133
139
 
134
140
  if free_surface == np.inf or water_depth == np.inf:
135
141
  raise NotImplementedError("FinGreen3D is only implemented for finite depth with a free surface.")
@@ -163,7 +169,7 @@ class FinGreen3D(AbstractGreenFunction):
163
169
  S, K
164
170
  )
165
171
 
166
- if mesh1 is mesh2:
172
+ if diagonal_term_in_double_layer:
167
173
  self.fortran_core.matrices.add_diagonal_term(
168
174
  mesh2.faces_centers, early_dot_product_normals, free_surface, K,
169
175
  )
@@ -197,8 +203,8 @@ class HAMS_GF(AbstractGreenFunction):
197
203
  def _repr_pretty_(self, p, cycle):
198
204
  p.text(self.__repr__())
199
205
 
200
- def evaluate(self, mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer=True, early_dot_product=True):
206
+ def evaluate(self, mesh1, mesh2, *, water_depth=np.inf, **kwargs):
201
207
  if water_depth == np.inf:
202
- return self.infinite_depth_gf.evaluate(mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer, early_dot_product)
208
+ return self.infinite_depth_gf.evaluate(mesh1, mesh2, water_depth=water_depth, **kwargs)
203
209
  else:
204
- return self.finite_depth_gf.evaluate(mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer, early_dot_product)
210
+ return self.finite_depth_gf.evaluate(mesh1, mesh2, water_depth=water_depth, **kwargs)
capytaine/io/legacy.py CHANGED
@@ -9,10 +9,9 @@ import numpy as np
9
9
 
10
10
  from capytaine.bem.solver import BEMSolver
11
11
  from capytaine.io.xarray import assemble_dataset
12
- from capytaine.io.mesh_writers import write_MAR
12
+ from capytaine.meshes.io import load_mesh
13
13
  from capytaine.bodies.bodies import FloatingBody
14
14
  from capytaine.bem.problems_and_results import DiffractionProblem, RadiationProblem
15
- from capytaine.meshes.geometry import Axis
16
15
 
17
16
  LOG = logging.getLogger(__name__)
18
17
 
@@ -47,7 +46,7 @@ def import_cal_file(filepath):
47
46
  spec.loader.exec_module(body_initialization)
48
47
  body = body_initialization.body
49
48
  else:
50
- body = FloatingBody.from_file(mesh_file)
49
+ body = FloatingBody(mesh=load_mesh(mesh_file, file_format='nemoh'))
51
50
 
52
51
  nb_dofs = int(cal_file.readline().split()[0])
53
52
  for i_dof in range(nb_dofs):
@@ -58,7 +57,7 @@ def import_cal_file(filepath):
58
57
  elif int(dof_data[0]) == 2:
59
58
  direction = np.array([float(x) for x in dof_data[1:4]])
60
59
  center_of_mass = np.array([float(x) for x in dof_data[4:7]])
61
- body.add_rotation_dof(Axis(vector=direction, point=center_of_mass))
60
+ body.add_rotation_dof(direction=direction, rotation_center=center_of_mass)
62
61
 
63
62
  nb_forces = int(cal_file.readline().split()[0])
64
63
  for i_force in range(nb_forces):
@@ -123,105 +122,6 @@ def import_cal_file(filepath):
123
122
  return problems
124
123
 
125
124
 
126
- def export_as_Nemoh_directory(problem, directory_name, omega_range=None):
127
- """Export radiation problems as Nemoh 2.0 directory (experimental).
128
-
129
- TODO: Diffraction problem.
130
-
131
- Parameters
132
- ----------
133
- problem : RadiationProblem
134
- the problem that should be exported
135
- directory_name : string
136
- path to the directory
137
- omega_range : list of float or array of float, optional
138
- the exported problem will be set up with the following linear range:
139
- linspace(min(omega_range), max(omega_range), len(omega_range))
140
- """
141
-
142
- if os.path.isdir(directory_name):
143
- LOG.warning(f"""Exporting problem in already existing directory: {directory_name}
144
- You might be overwriting existing files!""")
145
- else:
146
- os.makedirs(directory_name)
147
-
148
- # Export the mesh
149
- write_MAR(
150
- os.path.join(directory_name, f'{problem.body.name}.dat'),
151
- problem.body.mesh.vertices,
152
- problem.body.mesh.faces,
153
- # xOz_symmetry=isinstance(problem.body, ReflectionSymmetry)
154
- )
155
-
156
- # Set range of frequencies
157
- if omega_range is None:
158
- omega_nb_steps = 1
159
- omega_start = problem.omega
160
- omega_stop = problem.omega
161
- else:
162
- omega_nb_steps = len(omega_range)
163
- omega_start = min(omega_range)
164
- omega_stop = max(omega_range)
165
-
166
- # Write Nemoh.cal
167
- with open(os.path.join(directory_name, "Nemoh.cal"), "w") as nemoh_cal:
168
- nemoh_cal.write(
169
- DEFAULT_NEMOH_CAL.format(
170
- rho=problem.rho,
171
- g=problem.g,
172
- depth=problem.water_depth if problem.water_depth < np.inf else 0,
173
- mesh_filename=f'{problem.body.name}.dat',
174
- mesh_vertices=problem.body.mesh.nb_vertices,
175
- mesh_faces=problem.body.mesh.nb_faces,
176
- omega_nb_steps=omega_nb_steps,
177
- omega_start=omega_start,
178
- omega_stop=omega_stop,
179
- )
180
- )
181
-
182
- # Write input.txt
183
- with open(os.path.join(directory_name, "input.txt"), "w") as input_txt:
184
- input_txt.write(DEFAULT_INPUT_TXT)
185
-
186
- # Write ID.dat
187
- with open(os.path.join(directory_name, "ID.dat"), "w") as id_dat:
188
- id_dat.write(f"1\n.")
189
-
190
-
191
- DEFAULT_NEMOH_CAL = """--- Environment ------------------------------------------------------------------------------------------------------------------
192
- {rho} ! RHO ! KG/M**3 ! Fluid specific volume
193
- {g} ! G ! M/S**2 ! Gravity
194
- {depth} ! DEPTH ! M ! Water depth
195
- 0. 0. ! XEFF YEFF ! M ! Wave measurement point
196
- --- Description of floating bodies -----------------------------------------------------------------------------------------------
197
- 1 ! Number of bodies
198
- --- Body 1 -----------------------------------------------------------------------------------------------------------------------
199
- {mesh_filename}
200
- {mesh_vertices} {mesh_faces}
201
- 1 ! Number of degrees of freedom
202
- 1 0. 0. 1. 0. 0. 0. ! Heave
203
- 1 ! Number of resulting generalised forces
204
- 1 0. 0. 1. 0. 0. 0. ! Heave
205
- 0 ! Number of lines of additional information
206
- --- Load cases to be solved -------------------------------------------------------------------------------------------------------
207
- {omega_nb_steps} {omega_start} {omega_stop} ! Frequencies range
208
- 0 0. 0. ! Number of wave directions, Min and Max (degrees)
209
- --- Post processing ---------------------------------------------------------------------------------------------------------------
210
- 0 0.1 10. ! IRF ! IRF calculation (0 for no calculation), time step and duration
211
- 0 ! Show pressure
212
- 0 0. 180. ! Kochin function ! Number of directions of calculation (0 for no calculations), Min and Max (degrees)
213
- 0 0 100. 100. ! Free surface elevation ! Number of points in x direction (0 for no calcutions) and y direction and dimensions of domain in x and y direction
214
- """
215
-
216
- DEFAULT_INPUT_TXT = """--- Calculation parameters ------------------------------------------------------------------------------------
217
- 1 ! Indiq_solver ! - ! Solver (0) Direct Gauss (1) GMRES (2) GMRES with FMM acceleration (2 not implemented yet)
218
- 20 ! IRES ! - ! Restart parameter for GMRES
219
- 5.E-07 ! TOL_GMRES ! - ! Stopping criterion for GMRES
220
- 100 ! MAXIT ! - ! Maximum iterations for GMRES
221
- 1 ! Sav_potential ! - ! Save potential for visualization
222
- """
223
-
224
-
225
125
  def write_dataset_as_tecplot_files(results_directory, data):
226
126
  """Write some of the data from a xarray dataset into legacy tecplot file outputs."""
227
127
 
capytaine/io/xarray.py CHANGED
@@ -11,6 +11,7 @@ from datetime import datetime
11
11
  from itertools import product
12
12
  from collections import Counter
13
13
  from typing import Sequence, List, Union
14
+ from pathlib import Path
14
15
 
15
16
  import numpy as np
16
17
  import pandas as pd
@@ -18,6 +19,7 @@ import xarray as xr
18
19
 
19
20
  from capytaine import __version__
20
21
  from capytaine.bodies.bodies import FloatingBody
22
+ from capytaine.bodies.multibodies import Multibody
21
23
  from capytaine.bem.problems_and_results import (
22
24
  LinearPotentialFlowProblem, DiffractionProblem, RadiationProblem,
23
25
  LinearPotentialFlowResult, _default_parameters)
@@ -43,7 +45,7 @@ def _unsqueeze_dimensions(data_array, dimensions=None):
43
45
 
44
46
 
45
47
  def problems_from_dataset(dataset: xr.Dataset,
46
- bodies: Union[FloatingBody, Sequence[FloatingBody]],
48
+ bodies: Union[FloatingBody, Sequence[FloatingBody], Multibody, Sequence[Multibody]],
47
49
  ) -> List[LinearPotentialFlowProblem]:
48
50
  """Generate a list of problems from a test matrix.
49
51
 
@@ -64,7 +66,7 @@ def problems_from_dataset(dataset: xr.Dataset,
64
66
  ValueError
65
67
  if required fields are missing in the dataset
66
68
  """
67
- if isinstance(bodies, FloatingBody):
69
+ if isinstance(bodies, (FloatingBody, Multibody)):
68
70
  bodies = [bodies]
69
71
 
70
72
  # Should be done before looking for `frequency_keys`, otherwise
@@ -279,22 +281,24 @@ def kochin_data_array(results: Sequence[LinearPotentialFlowResult],
279
281
  compute_kochin(result, theta_range, **kwargs))
280
282
  ])
281
283
 
284
+ main_freq_type = Counter((res.provided_freq_type for res in results)).most_common(1)[0][0]
285
+
282
286
  kochin_data = xr.Dataset()
283
287
 
284
288
  if "RadiationResult" in set(records['kind']):
285
289
  radiation = _dataset_from_dataframe(
286
290
  records[records['kind'] == "RadiationResult"],
287
291
  variables=['kochin'],
288
- dimensions=['omega', 'radiating_dof', 'theta'],
292
+ dimensions=[main_freq_type, 'radiating_dof', 'theta'],
289
293
  optional_dims=['g', 'rho', 'body_name', 'water_depth', 'forward_speed', 'wave_direction']
290
294
  )
291
- kochin_data['kochin'] = radiation['kochin']
295
+ kochin_data['kochin_radiation'] = radiation['kochin']
292
296
 
293
297
  if "DiffractionResult" in set(records['kind']):
294
298
  diffraction = _dataset_from_dataframe(
295
299
  records[records['kind'] == "DiffractionResult"],
296
300
  ['kochin'],
297
- dimensions=['omega', 'wave_direction', 'theta'],
301
+ dimensions=[main_freq_type, 'wave_direction', 'theta'],
298
302
  optional_dims=['g', 'rho', 'body_name', 'water_depth', 'forward_speed']
299
303
  )
300
304
  kochin_data['kochin_diffraction'] = diffraction['kochin']
@@ -532,7 +536,7 @@ def assemble_dataset(results,
532
536
 
533
537
  def assemble_matrices(results):
534
538
  """Simplified version of assemble_dataset, returning only bare matrices.
535
- Meant mainly for teaching without introducing Xarray to beginers.
539
+ Meant mainly for teaching without introducing Xarray to beginners.
536
540
 
537
541
  Parameters
538
542
  ----------
@@ -662,6 +666,7 @@ def export_dataset(filename, dataset, format=None, **kwargs):
662
666
  (format is not None and format.lower() == "nemoh")
663
667
  ):
664
668
  from capytaine.io.legacy import write_dataset_as_tecplot_files
669
+ Path(filename).mkdir(exist_ok=True)
665
670
  write_dataset_as_tecplot_files(filename, dataset, **kwargs)
666
671
  else:
667
672
  raise ValueError("`export_dataset` could not infer export format based on filename or `format` argument.\n"
@@ -1,6 +1,2 @@
1
- # Copyright (C) 2017-2019 Matthieu Ancellin, based on the work of François Rongère
2
- # See LICENSE file at <https://github.com/mancellin/capytaine>
3
-
4
- from capytaine.meshes.meshes import Mesh
5
- from capytaine.meshes.collections import CollectionOfMeshes
6
- from capytaine.meshes.symmetric import ReflectionSymmetricMesh, TranslationalSymmetricMesh, AxialSymmetricMesh
1
+ from .meshes import Mesh
2
+ from .symmetric_meshes import ReflectionSymmetricMesh, RotationSymmetricMesh