capytaine 2.2.1__cp312-cp312-macosx_14_0_arm64.whl → 2.3.1__cp312-cp312-macosx_14_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. capytaine/.dylibs/libgfortran.5.dylib +0 -0
  3. capytaine/.dylibs/libquadmath.0.dylib +0 -0
  4. capytaine/__about__.py +1 -1
  5. capytaine/__init__.py +2 -1
  6. capytaine/bem/airy_waves.py +7 -2
  7. capytaine/bem/problems_and_results.py +91 -39
  8. capytaine/bem/solver.py +128 -40
  9. capytaine/bodies/bodies.py +46 -18
  10. capytaine/bodies/predefined/rectangles.py +2 -0
  11. capytaine/green_functions/FinGreen3D/.gitignore +1 -0
  12. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
  13. capytaine/green_functions/FinGreen3D/LICENSE +165 -0
  14. capytaine/green_functions/FinGreen3D/Makefile +16 -0
  15. capytaine/green_functions/FinGreen3D/README.md +24 -0
  16. capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
  17. capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
  18. capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
  19. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
  20. capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
  21. capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
  22. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
  23. capytaine/green_functions/abstract_green_function.py +55 -3
  24. capytaine/green_functions/delhommeau.py +205 -130
  25. capytaine/green_functions/hams.py +204 -0
  26. capytaine/green_functions/libs/Delhommeau_float32.cpython-312-darwin.so +0 -0
  27. capytaine/green_functions/libs/Delhommeau_float64.cpython-312-darwin.so +0 -0
  28. capytaine/io/bemio.py +14 -2
  29. capytaine/io/mesh_loaders.py +1 -1
  30. capytaine/io/wamit.py +479 -0
  31. capytaine/io/xarray.py +261 -117
  32. capytaine/matrices/linear_solvers.py +1 -1
  33. capytaine/meshes/clipper.py +1 -0
  34. capytaine/meshes/collections.py +19 -1
  35. capytaine/meshes/mesh_like_protocol.py +37 -0
  36. capytaine/meshes/meshes.py +28 -8
  37. capytaine/meshes/symmetric.py +89 -10
  38. capytaine/post_pro/kochin.py +4 -4
  39. capytaine/tools/lists_of_points.py +3 -3
  40. capytaine/tools/prony_decomposition.py +60 -4
  41. capytaine/tools/symbolic_multiplication.py +30 -4
  42. capytaine/tools/timer.py +66 -0
  43. {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/METADATA +6 -10
  44. capytaine-2.3.1.dist-info/RECORD +92 -0
  45. capytaine-2.3.1.dist-info/WHEEL +6 -0
  46. capytaine-2.2.1.dist-info/RECORD +0 -76
  47. capytaine-2.2.1.dist-info/WHEEL +0 -4
  48. {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/LICENSE +0 -0
  49. {capytaine-2.2.1.dist-info → capytaine-2.3.1.dist-info}/entry_points.txt +0 -0
Binary file
Binary file
Binary file
capytaine/__about__.py CHANGED
@@ -5,7 +5,7 @@ __all__ = ["__title__", "__description__", "__version__", "__author__", "__uri__
5
5
  __title__ = "capytaine"
6
6
  __description__ = """Python BEM solver for linear potential flow, based on Nemoh"""
7
7
 
8
- __version__ = "2.2.1"
8
+ __version__ = "2.3.1"
9
9
 
10
10
  __author__ = "Matthieu Ancellin"
11
11
  __uri__ = "https://github.com/capytaine/capytaine"
capytaine/__init__.py CHANGED
@@ -24,11 +24,12 @@ from capytaine.bem.problems_and_results import RadiationProblem, DiffractionProb
24
24
  from capytaine.bem.solver import BEMSolver
25
25
  from capytaine.bem.engines import BasicMatrixEngine, HierarchicalToeplitzMatrixEngine, HierarchicalPrecondMatrixEngine
26
26
  from capytaine.green_functions.delhommeau import Delhommeau, XieDelhommeau
27
+ from capytaine.green_functions.hams import LiangWuNoblesseGF, FinGreen3D, HAMS_GF
27
28
 
28
29
  from capytaine.post_pro.free_surfaces import FreeSurface
29
30
 
30
31
  from capytaine.io.mesh_loaders import load_mesh
31
- from capytaine.io.xarray import assemble_dataset
32
+ from capytaine.io.xarray import assemble_dataframe, assemble_dataset, assemble_matrices, export_dataset
32
33
 
33
34
  from capytaine.ui.rich import set_logging
34
35
 
@@ -22,6 +22,9 @@ def airy_waves_potential(points, pb):
22
22
  """
23
23
  points, output_shape = _normalize_points(points)
24
24
 
25
+ if float(pb.wavenumber) == 0.0 or float(pb.wavenumber) == np.inf:
26
+ return np.nan * np.ones(output_shape)
27
+
25
28
  x, y, z = points.T
26
29
  k = pb.wavenumber
27
30
  h = pb.water_depth
@@ -54,9 +57,11 @@ def airy_waves_velocity(points, pb):
54
57
  array of shape (3) or (N x 3)
55
58
  the velocity vectors
56
59
  """
57
-
58
60
  points, output_shape = _normalize_points(points)
59
61
 
62
+ if float(pb.wavenumber) == 0.0 or float(pb.wavenumber) == np.inf:
63
+ return np.nan * np.ones((*output_shape, 3))
64
+
60
65
  x, y, z = points.T
61
66
  k = pb.wavenumber
62
67
  h = pb.water_depth
@@ -79,7 +84,7 @@ def airy_waves_velocity(points, pb):
79
84
 
80
85
 
81
86
  def airy_waves_pressure(points, pb):
82
- return 1j * pb.omega * pb.rho * airy_waves_potential(points, pb)
87
+ return 1j * float(pb.omega) * pb.rho * airy_waves_potential(points, pb)
83
88
 
84
89
 
85
90
  def froude_krylov_force(pb):
@@ -24,8 +24,8 @@ _default_parameters = {'rho': 1000.0, 'g': 9.81, 'omega': 1.0,
24
24
  class LinearPotentialFlowProblem:
25
25
  """General class of a potential flow problem.
26
26
 
27
- At most one of the following parameter must be provided: omega, period, wavenumber or wavelength.
28
- Internally only omega is stored, hence setting another parameter can lead to small rounding errors.
27
+ At most one of the following parameters must be provided:
28
+ omega, freq, period, wavenumber or wavelength.
29
29
 
30
30
  Parameters
31
31
  ----------
@@ -39,6 +39,8 @@ class LinearPotentialFlowProblem:
39
39
  The position of the sea bottom (deprecated: please prefer setting water_depth)
40
40
  omega: float, optional
41
41
  The angular frequency of the waves in rad/s
42
+ freq: float, optional
43
+ The frequency of the waves in Hz
42
44
  period: float, optional
43
45
  The period of the waves in s
44
46
  wavenumber: float, optional
@@ -59,7 +61,7 @@ class LinearPotentialFlowProblem:
59
61
  body=None,
60
62
  free_surface=_default_parameters['free_surface'],
61
63
  water_depth=None, sea_bottom=None,
62
- omega=None, period=None, wavenumber=None, wavelength=None,
64
+ omega=None, freq=None, period=None, wavenumber=None, wavelength=None,
63
65
  forward_speed=_default_parameters['forward_speed'],
64
66
  rho=_default_parameters['rho'],
65
67
  g=_default_parameters['g'],
@@ -76,14 +78,14 @@ class LinearPotentialFlowProblem:
76
78
  self.boundary_condition = boundary_condition
77
79
 
78
80
  self.water_depth = _get_water_depth(free_surface, water_depth, sea_bottom, default_water_depth=_default_parameters["water_depth"])
79
- self.omega, self.period, self.wavenumber, self.wavelength, self.provided_freq_type = \
80
- self._get_frequencies(omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength)
81
+ self.omega, self.freq, self.period, self.wavenumber, self.wavelength, self.provided_freq_type = \
82
+ self._get_frequencies(omega=omega, freq=freq, period=period, wavenumber=wavenumber, wavelength=wavelength)
81
83
 
82
84
  self._check_data()
83
85
 
84
86
  if forward_speed != 0.0:
85
87
  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, _ = \
88
+ self.encounter_omega, self.encounter_freq, self.encounter_period, self.encounter_wavenumber, self.encounter_wavelength, _ = \
87
89
  self._get_frequencies(omega=abs(dopplered_omega))
88
90
 
89
91
  if dopplered_omega >= 0.0:
@@ -92,18 +94,19 @@ class LinearPotentialFlowProblem:
92
94
  self.encounter_wave_direction = self.wave_direction + np.pi
93
95
  else:
94
96
  self.encounter_omega = self.omega
97
+ self.encounter_freq = self.freq
95
98
  self.encounter_period = self.period
96
99
  self.encounter_wavenumber = self.wavenumber
97
100
  self.encounter_wavelength = self.wavelength
98
101
  self.encounter_wave_direction = self.wave_direction
99
102
 
100
103
 
101
- def _get_frequencies(self, *, omega=None, period=None, wavenumber=None, wavelength=None):
102
- frequency_data = dict(omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength)
103
- nb_provided_frequency_data = 4 - list(frequency_data.values()).count(None)
104
+ def _get_frequencies(self, *, omega=None, freq=None, period=None, wavenumber=None, wavelength=None):
105
+ frequency_data = dict(omega=omega, freq=freq, period=period, wavenumber=wavenumber, wavelength=wavelength)
106
+ nb_provided_frequency_data = len(frequency_data) - list(frequency_data.values()).count(None)
104
107
 
105
108
  if nb_provided_frequency_data > 1:
106
- raise ValueError("Settings a problem requires at most one of the following: omega (angular frequency) OR period OR wavenumber OR wavelength.\n"
109
+ raise ValueError("Settings a problem requires at most one of the following: omega (angular frequency) OR freq (in Hz) OR period OR wavenumber OR wavelength.\n"
107
110
  "Received {} of them: {}".format(nb_provided_frequency_data, {k: v for k, v in frequency_data.items() if v is not None}))
108
111
 
109
112
  if nb_provided_frequency_data == 0:
@@ -112,27 +115,35 @@ class LinearPotentialFlowProblem:
112
115
  else:
113
116
  provided_freq_type = [k for (k, v) in frequency_data.items() if v is not None][0]
114
117
 
115
- if ((float(frequency_data[provided_freq_type]) == 0.0 and provided_freq_type in {'omega', 'wavenumber'})
118
+ if ((float(frequency_data[provided_freq_type]) == 0.0 and provided_freq_type in {'omega', 'freq', 'wavenumber'})
116
119
  or (float(frequency_data[provided_freq_type]) == np.inf and provided_freq_type in {'period', 'wavelength'})):
117
120
  omega = SymbolicMultiplication("0")
121
+ freq = SymbolicMultiplication("0")
118
122
  wavenumber = SymbolicMultiplication("0")
119
123
  period = SymbolicMultiplication("∞")
120
124
  wavelength = SymbolicMultiplication("∞")
121
125
  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'})):
126
+ or (float(frequency_data[provided_freq_type]) == np.inf and provided_freq_type in {'omega', 'freq', 'wavenumber'})):
123
127
  omega = SymbolicMultiplication("∞")
128
+ freq = SymbolicMultiplication("∞")
124
129
  wavenumber = SymbolicMultiplication("∞")
125
130
  period = SymbolicMultiplication("0")
126
131
  wavelength = SymbolicMultiplication("0")
127
132
  else:
128
133
 
129
- if provided_freq_type in {'omega', 'period'}:
134
+ if provided_freq_type in {'omega', 'freq', 'period'}:
130
135
  if provided_freq_type == 'omega':
131
136
  omega = frequency_data['omega']
132
137
  period = 2*np.pi/omega
138
+ freq = omega/2/np.pi
139
+ elif provided_freq_type == 'freq':
140
+ freq = frequency_data['freq']
141
+ omega = 2*np.pi*freq
142
+ period = 1/freq
133
143
  else: # provided_freq_type is 'period'
134
144
  period = frequency_data['period']
135
145
  omega = 2*np.pi/period
146
+ freq = 1/period
136
147
 
137
148
  if self.water_depth == np.inf:
138
149
  wavenumber = omega**2/self.g
@@ -150,8 +161,9 @@ class LinearPotentialFlowProblem:
150
161
 
151
162
  omega = np.sqrt(self.g*wavenumber*np.tanh(wavenumber*self.water_depth))
152
163
  period = 2*np.pi/omega
164
+ freq = 1/period
153
165
 
154
- return omega, period, wavenumber, wavelength, provided_freq_type
166
+ return omega, freq, period, wavenumber, wavelength, provided_freq_type
155
167
 
156
168
  def _check_data(self):
157
169
  """Sanity checks on the data."""
@@ -168,7 +180,6 @@ class LinearPotentialFlowProblem:
168
180
  "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
181
 
170
182
  if self.free_surface == np.inf and self.water_depth != np.inf:
171
-
172
183
  raise NotImplementedError(
173
184
  "Problems with a sea bottom but no free surface have not been implemented."
174
185
  )
@@ -177,11 +188,6 @@ class LinearPotentialFlowProblem:
177
188
  raise ValueError("`water_depth` should be strictly positive (provided water depth: {self.water_depth}).")
178
189
 
179
190
  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
191
  if self.forward_speed != 0.0:
186
192
  raise NotImplementedError(
187
193
  f"omega={float(self.omega)} is only implemented without forward speed (provided forward speed: {self.forward_speed})."
@@ -193,6 +199,8 @@ class LinearPotentialFlowProblem:
193
199
  or len(self.body.mesh.faces) == 0):
194
200
  raise ValueError(f"The mesh of the body {self.body.__short_str__()} is empty.")
195
201
 
202
+ self.body._check_dofs_shape_consistency()
203
+
196
204
  panels_above_fs = self.body.mesh.faces_centers[:, 2] >= self.free_surface + 1e-8
197
205
  panels_below_sb = self.body.mesh.faces_centers[:, 2] <= -self.water_depth
198
206
  if (any(panels_above_fs) or any(panels_below_sb)):
@@ -231,8 +239,11 @@ class LinearPotentialFlowProblem:
231
239
  def _asdict(self):
232
240
  return {"body_name": self.body_name,
233
241
  "water_depth": self.water_depth,
242
+ "free_surface": self.free_surface,
234
243
  "omega": float(self.omega),
244
+ "freq": float(self.freq),
235
245
  "encounter_omega": float(self.encounter_omega),
246
+ "encounter_freq": float(self.encounter_freq),
236
247
  "period": float(self.period),
237
248
  "wavelength": float(self.wavelength),
238
249
  "wavenumber": float(self.wavenumber),
@@ -322,8 +333,11 @@ class LinearPotentialFlowProblem:
322
333
  # TODO: let the user choose the influenced dofs
323
334
  return self.body.dofs if self.body is not None else set()
324
335
 
325
- def make_results_container(self):
326
- return LinearPotentialFlowResult(self)
336
+ def make_results_container(self, *args, **kwargs):
337
+ return LinearPotentialFlowResult(self, *args, **kwargs)
338
+
339
+ def make_failed_results_container(self, *args, **kwargs):
340
+ return FailedLinearPotentialFlowResult(self, *args, **kwargs)
327
341
 
328
342
 
329
343
  class DiffractionProblem(LinearPotentialFlowProblem):
@@ -334,31 +348,30 @@ class DiffractionProblem(LinearPotentialFlowProblem):
334
348
  body=None,
335
349
  free_surface=_default_parameters['free_surface'],
336
350
  water_depth=None, sea_bottom=None,
337
- omega=None, period=None, wavenumber=None, wavelength=None,
351
+ omega=None, freq=None, period=None, wavenumber=None, wavelength=None,
338
352
  forward_speed=_default_parameters['forward_speed'],
339
353
  rho=_default_parameters['rho'],
340
354
  g=_default_parameters['g'],
341
355
  wave_direction=_default_parameters['wave_direction']):
342
356
 
343
357
  super().__init__(body=body, free_surface=free_surface, water_depth=water_depth, sea_bottom=sea_bottom,
344
- omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength, wave_direction=wave_direction,
358
+ omega=omega, freq=freq, period=period, wavenumber=wavenumber, wavelength=wavelength, wave_direction=wave_direction,
345
359
  forward_speed=forward_speed, rho=rho, g=g)
346
360
 
347
- if float(self.omega) in {0.0, np.inf}:
348
- raise NotImplementedError("DiffractionProblem does not support zero or infinite frequency.")
349
-
350
361
  if self.body is not None:
351
362
 
352
- self.boundary_condition = -(
363
+ self.boundary_condition = np.zeros(
364
+ shape=(self.body.mesh_including_lid.nb_faces,),
365
+ dtype=np.complex128
366
+ )
367
+
368
+ self.boundary_condition[self.body.hull_mask] = -(
353
369
  airy_waves_velocity(self.body.mesh.faces_centers, self)
354
370
  * self.body.mesh.faces_normals
355
371
  ).sum(axis=1)
356
372
  # Note that even with forward speed, this is computed based on the
357
373
  # frequency and not the encounter frequency.
358
374
 
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)])
361
-
362
375
  if len(self.body.dofs) == 0:
363
376
  LOG.warning(f"The body {self.body.name} used in diffraction problem has no dofs!")
364
377
 
@@ -371,6 +384,9 @@ class DiffractionProblem(LinearPotentialFlowProblem):
371
384
  def make_results_container(self, *args, **kwargs):
372
385
  return DiffractionResult(self, *args, **kwargs)
373
386
 
387
+ def make_failed_results_container(self, *args, **kwargs):
388
+ return FailedDiffractionResult(self, *args, **kwargs)
389
+
374
390
 
375
391
  class RadiationProblem(LinearPotentialFlowProblem):
376
392
  """Particular LinearPotentialFlowProblem whose boundary conditions have
@@ -379,7 +395,7 @@ class RadiationProblem(LinearPotentialFlowProblem):
379
395
  def __init__(self, *, body=None,
380
396
  free_surface=_default_parameters['free_surface'],
381
397
  water_depth=None, sea_bottom=None,
382
- omega=None, period=None, wavenumber=None, wavelength=None,
398
+ omega=None, freq=None, period=None, wavenumber=None, wavelength=None,
383
399
  forward_speed=_default_parameters['forward_speed'],
384
400
  wave_direction=_default_parameters['wave_direction'],
385
401
  rho=_default_parameters['rho'],
@@ -389,7 +405,7 @@ class RadiationProblem(LinearPotentialFlowProblem):
389
405
  self.radiating_dof = radiating_dof
390
406
 
391
407
  super().__init__(body=body, free_surface=free_surface, water_depth=water_depth, sea_bottom=sea_bottom,
392
- omega=omega, period=period, wavenumber=wavenumber, wavelength=wavelength,
408
+ omega=omega, freq=freq, period=period, wavenumber=wavenumber, wavelength=wavelength,
393
409
  wave_direction=wave_direction, forward_speed=forward_speed, rho=rho, g=g)
394
410
 
395
411
  if self.body is not None:
@@ -407,7 +423,16 @@ class RadiationProblem(LinearPotentialFlowProblem):
407
423
 
408
424
  dof = self.body.dofs[self.radiating_dof]
409
425
 
410
- self.boundary_condition = -1j * self.encounter_omega * np.sum(dof * self.body.mesh.faces_normals, axis=1)
426
+ self.boundary_condition = self.encounter_omega * np.zeros(
427
+ shape=(self.body.mesh_including_lid.nb_faces,),
428
+ dtype=np.complex128
429
+ )
430
+ # The multiplication by encounter_omega is just a programming trick to ensure that boundary_condition
431
+ # is implemented with the correct type (for zero and infinite frequencies), it does not affect the value.
432
+ # Below the value is update on the hull. It remains zero on the lid.
433
+
434
+ displacement_on_face = np.sum(dof * self.body.mesh.faces_normals, axis=1) # This is a dot product on each face
435
+ self.boundary_condition[self.body.hull_mask] = -1j * self.encounter_omega * displacement_on_face
411
436
 
412
437
  if self.forward_speed != 0.0:
413
438
  if self.radiating_dof.lower() == "pitch":
@@ -422,10 +447,9 @@ class RadiationProblem(LinearPotentialFlowProblem):
422
447
  "Only radiating dofs with name in {'Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw'} are supported.\n"
423
448
  f"Got instead `radiating_dof={self.radiating_dof}`"
424
449
  )
425
- self.boundary_condition += self.forward_speed * ddofdx_dot_n
426
450
 
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)])
451
+ self.boundary_condition[self.body.hull_mask] += self.forward_speed * ddofdx_dot_n
452
+
429
453
 
430
454
 
431
455
  def _astuple(self):
@@ -448,6 +472,9 @@ class RadiationProblem(LinearPotentialFlowProblem):
448
472
  def make_results_container(self, *args, **kwargs):
449
473
  return RadiationResult(self, *args, **kwargs)
450
474
 
475
+ def make_failed_results_container(self, *args, **kwargs):
476
+ return FailedRadiationResult(self, *args, **kwargs)
477
+
451
478
 
452
479
  class LinearPotentialFlowResult:
453
480
 
@@ -465,12 +492,14 @@ class LinearPotentialFlowResult:
465
492
  self.body = self.problem.body
466
493
  self.free_surface = self.problem.free_surface
467
494
  self.omega = self.problem.omega
495
+ self.freq = self.problem.freq
468
496
  self.period = self.problem.period
469
497
  self.wavenumber = self.problem.wavenumber
470
498
  self.wavelength = self.problem.wavelength
471
499
  self.forward_speed = self.problem.forward_speed
472
500
  self.wave_direction = self.problem.wave_direction
473
501
  self.encounter_omega = self.problem.encounter_omega
502
+ self.encounter_freq = self.problem.encounter_freq
474
503
  self.encounter_period = self.problem.encounter_period
475
504
  self.encounter_wavenumber = self.problem.encounter_wavenumber
476
505
  self.encounter_wavelength = self.problem.encounter_wavelength
@@ -495,6 +524,13 @@ class LinearPotentialFlowResult:
495
524
  __rich_repr__ = LinearPotentialFlowProblem.__rich_repr__
496
525
 
497
526
 
527
+ class FailedLinearPotentialFlowResult(LinearPotentialFlowResult):
528
+ def __init__(self, problem, exception):
529
+ LinearPotentialFlowResult.__init__(self, problem)
530
+ self.forces = {dof: np.nan + 1j*np.nan for dof in self.influenced_dofs}
531
+ self.exception = exception
532
+
533
+
498
534
  class DiffractionResult(LinearPotentialFlowResult):
499
535
 
500
536
  def __init__(self, problem, *args, **kwargs):
@@ -510,10 +546,18 @@ class DiffractionResult(LinearPotentialFlowResult):
510
546
  return [dict(**params,
511
547
  influenced_dof=dof,
512
548
  diffraction_force=self.forces[dof],
513
- Froude_Krylov_force=FK[dof])
549
+ Froude_Krylov_force=FK[dof],
550
+ kind="DiffractionResult")
514
551
  for dof in self.influenced_dofs]
515
552
 
516
553
 
554
+ class FailedDiffractionResult(DiffractionResult):
555
+ def __init__(self, problem, exception):
556
+ DiffractionResult.__init__(self, problem)
557
+ self.forces = {dof: np.nan for dof in self.influenced_dofs}
558
+ self.exception = exception
559
+
560
+
517
561
  class RadiationResult(LinearPotentialFlowResult):
518
562
 
519
563
  def __init__(self, problem, *args, **kwargs):
@@ -544,5 +588,13 @@ class RadiationResult(LinearPotentialFlowResult):
544
588
  return [dict(params,
545
589
  influenced_dof=dof,
546
590
  added_mass=self.added_mass[dof],
547
- radiation_damping=self.radiation_damping[dof])
591
+ radiation_damping=self.radiation_damping[dof],
592
+ kind="RadiationResult")
548
593
  for dof in self.influenced_dofs]
594
+
595
+
596
+ class FailedRadiationResult(RadiationResult):
597
+ def __init__(self, problem, exception):
598
+ RadiationResult.__init__(self, problem)
599
+ self.forces = {dof: np.nan + 1j*np.nan for dof in self.influenced_dofs}
600
+ self.exception = exception