capytaine 2.2.1__cp312-cp312-macosx_13_0_x86_64.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 (76) 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 +16 -0
  5. capytaine/__init__.py +35 -0
  6. capytaine/bem/__init__.py +0 -0
  7. capytaine/bem/airy_waves.py +106 -0
  8. capytaine/bem/engines.py +441 -0
  9. capytaine/bem/problems_and_results.py +548 -0
  10. capytaine/bem/solver.py +506 -0
  11. capytaine/bodies/__init__.py +4 -0
  12. capytaine/bodies/bodies.py +1193 -0
  13. capytaine/bodies/dofs.py +19 -0
  14. capytaine/bodies/predefined/__init__.py +6 -0
  15. capytaine/bodies/predefined/cylinders.py +151 -0
  16. capytaine/bodies/predefined/rectangles.py +109 -0
  17. capytaine/bodies/predefined/spheres.py +70 -0
  18. capytaine/green_functions/__init__.py +2 -0
  19. capytaine/green_functions/abstract_green_function.py +12 -0
  20. capytaine/green_functions/delhommeau.py +432 -0
  21. capytaine/green_functions/libs/Delhommeau_float32.cpython-312-darwin.so +0 -0
  22. capytaine/green_functions/libs/Delhommeau_float64.cpython-312-darwin.so +0 -0
  23. capytaine/green_functions/libs/__init__.py +0 -0
  24. capytaine/io/__init__.py +0 -0
  25. capytaine/io/bemio.py +141 -0
  26. capytaine/io/legacy.py +328 -0
  27. capytaine/io/mesh_loaders.py +1086 -0
  28. capytaine/io/mesh_writers.py +692 -0
  29. capytaine/io/meshio.py +38 -0
  30. capytaine/io/xarray.py +524 -0
  31. capytaine/matrices/__init__.py +16 -0
  32. capytaine/matrices/block.py +592 -0
  33. capytaine/matrices/block_toeplitz.py +325 -0
  34. capytaine/matrices/builders.py +89 -0
  35. capytaine/matrices/linear_solvers.py +232 -0
  36. capytaine/matrices/low_rank.py +395 -0
  37. capytaine/meshes/__init__.py +6 -0
  38. capytaine/meshes/clipper.py +464 -0
  39. capytaine/meshes/collections.py +324 -0
  40. capytaine/meshes/geometry.py +409 -0
  41. capytaine/meshes/meshes.py +870 -0
  42. capytaine/meshes/predefined/__init__.py +6 -0
  43. capytaine/meshes/predefined/cylinders.py +314 -0
  44. capytaine/meshes/predefined/rectangles.py +261 -0
  45. capytaine/meshes/predefined/spheres.py +62 -0
  46. capytaine/meshes/properties.py +276 -0
  47. capytaine/meshes/quadratures.py +80 -0
  48. capytaine/meshes/quality.py +448 -0
  49. capytaine/meshes/surface_integrals.py +63 -0
  50. capytaine/meshes/symmetric.py +383 -0
  51. capytaine/post_pro/__init__.py +6 -0
  52. capytaine/post_pro/free_surfaces.py +88 -0
  53. capytaine/post_pro/impedance.py +92 -0
  54. capytaine/post_pro/kochin.py +54 -0
  55. capytaine/post_pro/rao.py +60 -0
  56. capytaine/tools/__init__.py +0 -0
  57. capytaine/tools/cache_on_disk.py +26 -0
  58. capytaine/tools/deprecation_handling.py +18 -0
  59. capytaine/tools/lists_of_points.py +52 -0
  60. capytaine/tools/lru_cache.py +49 -0
  61. capytaine/tools/optional_imports.py +27 -0
  62. capytaine/tools/prony_decomposition.py +94 -0
  63. capytaine/tools/symbolic_multiplication.py +123 -0
  64. capytaine/ui/__init__.py +0 -0
  65. capytaine/ui/cli.py +28 -0
  66. capytaine/ui/rich.py +5 -0
  67. capytaine/ui/vtk/__init__.py +3 -0
  68. capytaine/ui/vtk/animation.py +329 -0
  69. capytaine/ui/vtk/body_viewer.py +28 -0
  70. capytaine/ui/vtk/helpers.py +82 -0
  71. capytaine/ui/vtk/mesh_viewer.py +461 -0
  72. capytaine-2.2.1.dist-info/LICENSE +674 -0
  73. capytaine-2.2.1.dist-info/METADATA +754 -0
  74. capytaine-2.2.1.dist-info/RECORD +76 -0
  75. capytaine-2.2.1.dist-info/WHEEL +4 -0
  76. capytaine-2.2.1.dist-info/entry_points.txt +3 -0
capytaine/io/meshio.py ADDED
@@ -0,0 +1,38 @@
1
+ """Importing mesh from meshio"""
2
+ # Copyright (C) 2017-2022 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
4
+ import logging
5
+
6
+ import numpy as np
7
+
8
+ from capytaine.tools.optional_imports import import_optional_dependency
9
+ from capytaine.meshes.meshes import Mesh
10
+
11
+ LOG = logging.getLogger(__name__)
12
+
13
+ def load_from_meshio(mesh, name=None):
14
+ """Create a Mesh from a meshio mesh object."""
15
+ meshio = import_optional_dependency("meshio")
16
+ if not isinstance(mesh, meshio._mesh.Mesh):
17
+ raise TypeError('mesh must be of type meshio._mesh.Mesh, received {:}'.format(type(mesh)))
18
+
19
+ def all_faces_as_quads(cells):
20
+ all_faces = []
21
+ if 'quad' in cells:
22
+ all_faces.append(cells['quad'])
23
+ if 'triangle' in cells:
24
+ num_triangles = len(mesh.cells_dict['triangle'])
25
+ LOG.info("Stored {:} triangle faces as quadrilaterals".format(num_triangles))
26
+ triangles_as_quads = np.empty((cells['triangle'].shape[0], 4), dtype=int)
27
+ triangles_as_quads[:, :3] = cells['triangle'][:, :]
28
+ triangles_as_quads[:, 3] = cells['triangle'][:, 2] # Repeat one node to make a quad
29
+ all_faces.append(triangles_as_quads)
30
+ return np.concatenate(all_faces)
31
+
32
+ if name is None:
33
+ name = f'mesh_from_meshio_{next(Mesh._ids)}'
34
+
35
+ mesh = Mesh(vertices=mesh.points, faces=all_faces_as_quads(mesh.cells_dict), name=name)
36
+ mesh.heal_mesh()
37
+
38
+ return mesh
capytaine/io/xarray.py ADDED
@@ -0,0 +1,524 @@
1
+ """Tools to use xarray Datasets as inputs and outputs.
2
+
3
+ .. todo:: This module could be tidied up a bit and some methods merged or
4
+ uniformized.
5
+ """
6
+ # Copyright (C) 2017-2019 Matthieu Ancellin
7
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
8
+
9
+ import logging
10
+ from datetime import datetime
11
+ from itertools import product
12
+ from collections import Counter
13
+ from typing import Sequence, List, Union
14
+
15
+ import numpy as np
16
+ import pandas as pd
17
+ import xarray as xr
18
+
19
+ from capytaine import __version__
20
+ from capytaine.bodies.bodies import FloatingBody
21
+ from capytaine.bem.problems_and_results import (
22
+ LinearPotentialFlowProblem, DiffractionProblem, RadiationProblem,
23
+ LinearPotentialFlowResult, _default_parameters)
24
+ from capytaine.post_pro.kochin import compute_kochin
25
+ from capytaine.io.bemio import dataframe_from_bemio
26
+
27
+
28
+ LOG = logging.getLogger(__name__)
29
+
30
+
31
+ #########################
32
+ # Reading test matrix #
33
+ #########################
34
+
35
+ def problems_from_dataset(dataset: xr.Dataset,
36
+ bodies: Union[FloatingBody, Sequence[FloatingBody]],
37
+ ) -> List[LinearPotentialFlowProblem]:
38
+ """Generate a list of problems from a test matrix.
39
+
40
+ Parameters
41
+ ----------
42
+ dataset : xarray Dataset
43
+ Test matrix containing the problems parameters.
44
+ bodies : FloatingBody or list of FloatingBody
45
+ The bodies on which the computations of the test matrix will be applied.
46
+ They should all have different names.
47
+
48
+ Returns
49
+ -------
50
+ list of LinearPotentialFlowProblem
51
+
52
+ Raises
53
+ ------
54
+ ValueError
55
+ if required fields are missing in the dataset
56
+ """
57
+ if isinstance(bodies, FloatingBody):
58
+ bodies = [bodies]
59
+
60
+ # Should be done before looking for `frequency_keys`, otherwise
61
+ # frequencies provided as a scalar dimension will be skipped.
62
+ dataset = _unsqueeze_dimensions(dataset)
63
+
64
+ # SANITY CHECKS
65
+ assert len(list(set(body.name for body in bodies))) == len(bodies), \
66
+ "All bodies should have different names."
67
+
68
+ # Warn user in case of key with unrecognized name (e.g. misspells)
69
+ keys_in_dataset = set(dataset.dims)
70
+ accepted_keys = {'wave_direction', 'radiating_dof', 'influenced_dof',
71
+ 'body_name', 'omega', 'period', 'wavelength', 'wavenumber',
72
+ 'forward_speed', 'water_depth', 'rho', 'g', 'theta'}
73
+ unrecognized_keys = keys_in_dataset.difference(accepted_keys)
74
+ if len(unrecognized_keys) > 0:
75
+ LOG.warning(f"Unrecognized key(s) in dataset: {unrecognized_keys}")
76
+
77
+ if ("radiating_dof" not in keys_in_dataset) and ("wave_direction" not in keys_in_dataset):
78
+ raise ValueError("Neither 'radiating_dof' nor 'wave_direction' has been provided in the dataset. "
79
+ "No linear potential flow problem can be inferred.")
80
+
81
+ frequency_keys = keys_in_dataset & {'omega', 'period', 'wavelength', 'wavenumber'}
82
+ if len(frequency_keys) > 1:
83
+ raise ValueError("Setting problems requires at most one of the following: omega (angular frequency) OR period OR wavenumber OR wavelength.\n"
84
+ "Received {}".format(frequency_keys))
85
+ # END SANITY CHECKS
86
+
87
+ if len(frequency_keys) == 0:
88
+ freq_type = "omega"
89
+ freq_range = [_default_parameters['omega']]
90
+ else: # len(frequency_keys) == 1
91
+ freq_type = list(frequency_keys)[0] # Get the only item
92
+ freq_range = dataset[freq_type].data
93
+
94
+ water_depth_range = dataset['water_depth'].data if 'water_depth' in dataset else [_default_parameters['water_depth']]
95
+ rho_range = dataset['rho'].data if 'rho' in dataset else [_default_parameters['rho']]
96
+ g_range = dataset['g'].data if 'g' in dataset else [_default_parameters['g']]
97
+ forward_speed_range = dataset['forward_speed'] if 'forward_speed' in dataset else [_default_parameters['forward_speed']]
98
+
99
+ wave_direction_range = dataset['wave_direction'].data if 'wave_direction' in dataset else None
100
+ radiating_dofs = dataset['radiating_dof'].data.astype(object) if 'radiating_dof' in dataset else None
101
+ # astype(object) is meant to convert Numpy internal string type numpy.str_ to Python general string type.
102
+
103
+ if 'body_name' in dataset:
104
+ assert set(dataset['body_name'].data) <= {body.name for body in bodies}, \
105
+ "Some body named in the dataset was not given as argument to `problems_from_dataset`."
106
+ body_range = {body.name: body for body in bodies if body.name in dataset['body_name'].data}
107
+ # Only the bodies listed in the dataset have been kept
108
+ else:
109
+ body_range = {body.name: body for body in bodies}
110
+
111
+ problems = []
112
+ if wave_direction_range is not None:
113
+ for freq, wave_direction, water_depth, body_name, forward_speed, rho, g \
114
+ in product(freq_range, wave_direction_range, water_depth_range, body_range, forward_speed_range, rho_range, g_range):
115
+ if freq not in {0.0, np.inf}:
116
+ problems.append(
117
+ DiffractionProblem(body=body_range[body_name], **{freq_type: freq},
118
+ wave_direction=wave_direction, water_depth=water_depth,
119
+ forward_speed=forward_speed, rho=rho, g=g)
120
+ )
121
+ elif freq in {0.0, np.inf} and radiating_dofs is not None:
122
+ # Diffraction problems are not defined for 0 and infinite frequency.
123
+ # But we don't want the whole batch to fail, as these frequencies are there for the radiation problems.
124
+ # The excitation force will be NaN for these frequencies in the resulting dataset.
125
+ pass
126
+ else:
127
+ raise ValueError("Zero and infinite frequencies are not defined when solving only diffraction problems.")
128
+
129
+ if radiating_dofs is not None:
130
+ for freq, radiating_dof, water_depth, body_name, forward_speed, rho, g \
131
+ in product(freq_range, radiating_dofs, water_depth_range, body_range, forward_speed_range, rho_range, g_range):
132
+ if forward_speed == 0.0:
133
+ problems.append(
134
+ RadiationProblem(body=body_range[body_name], **{freq_type: freq},
135
+ radiating_dof=radiating_dof, water_depth=water_depth,
136
+ forward_speed=forward_speed, rho=rho, g=g)
137
+ )
138
+ else:
139
+ if wave_direction_range is None:
140
+ LOG.warning("Dataset contains non-zero forward speed (forward_speed=%.2f) but no wave_direction has been provided. Wave direction of 0 rad (x-axis) has been assumed.", forward_speed)
141
+ wave_direction_range = [0.0]
142
+ for wave_direction in wave_direction_range:
143
+ problems.append(
144
+ RadiationProblem(body=body_range[body_name], **{freq_type: freq},
145
+ radiating_dof=radiating_dof, water_depth=water_depth,
146
+ forward_speed=forward_speed, wave_direction=wave_direction,
147
+ rho=rho, g=g)
148
+ )
149
+
150
+ return sorted(problems)
151
+
152
+
153
+ def _squeeze_dimensions(data_array, dimensions=None):
154
+ """Remove dimensions if they are of size 1. The coordinates become scalar coordinates."""
155
+ if dimensions is None:
156
+ dimensions = data_array.dims
157
+ for dim in dimensions:
158
+ if len(data_array[dim]) == 1:
159
+ data_array = data_array.squeeze(dim, drop=False)
160
+ return data_array
161
+
162
+
163
+ def _unsqueeze_dimensions(data_array, dimensions=None):
164
+ """Add scalar coordinates as dimensions of size 1."""
165
+ if dimensions is None:
166
+ dimensions = list(data_array.coords.keys())
167
+ for dim in dimensions:
168
+ if len(data_array.coords[dim].values.shape) == 0:
169
+ data_array = xr.concat([data_array], dim=dim)
170
+ return data_array
171
+
172
+
173
+ ######################
174
+ # Dataset creation #
175
+ ######################
176
+
177
+ def _dataset_from_dataframe(df: pd.DataFrame,
178
+ variables: Union[str, Sequence[str]],
179
+ dimensions: Sequence[str],
180
+ optional_dims: Sequence[str],
181
+ ) -> Union[xr.DataArray, xr.Dataset]:
182
+ """Transform a pandas.Dataframe into a xarray.Dataset.
183
+
184
+ Parameters
185
+ ----------
186
+ df: pandas.DataFrame
187
+ the input dataframe
188
+ variables: string or sequence of strings
189
+ the variables that will be stored in the output dataset.
190
+ If a single name is provided, a DataArray of this variable will be provided instead.
191
+ dimensions: sequence of strings
192
+ Names of dimensions the variables depends on.
193
+ They will always appear as dimension in the output dataset.
194
+ optional_dims: sequence of strings
195
+ Names of dimensions the variables depends on.
196
+ They will appears as dimension in the output dataset only if they have
197
+ more than one different values.
198
+ """
199
+
200
+ for variable_name in variables:
201
+ df = df[df[variable_name].notnull()].dropna(axis='columns') # Keep only records with non null values of all the variables
202
+ df = df.drop_duplicates(optional_dims + dimensions)
203
+ df = df.set_index(optional_dims + dimensions)
204
+
205
+ da = df.to_xarray()[variables]
206
+ da = _squeeze_dimensions(da, dimensions=optional_dims)
207
+ return da
208
+
209
+
210
+ def hydrostatics_dataset(bodies: Sequence[FloatingBody]) -> xr.Dataset:
211
+ """Create a dataset by looking for 'inertia_matrix' and 'hydrostatic_stiffness'
212
+ for each of the bodies in the list passed as argument.
213
+ """
214
+ dataset = xr.Dataset()
215
+ for body_property in ['inertia_matrix', 'hydrostatic_stiffness']:
216
+ bodies_properties = {body.name: body.__getattribute__(body_property) for body in bodies if hasattr(body, body_property)}
217
+ if len(bodies_properties) > 0:
218
+ bodies_properties = xr.concat(bodies_properties.values(), pd.Index(bodies_properties.keys(), name='body_name'))
219
+ bodies_properties = _squeeze_dimensions(bodies_properties, dimensions=['body_name'])
220
+ dataset = xr.merge([dataset, {body_property: bodies_properties}])
221
+ return dataset
222
+
223
+
224
+ def kochin_data_array(results: Sequence[LinearPotentialFlowResult],
225
+ theta_range: Sequence[float],
226
+ **kwargs,
227
+ ) -> xr.Dataset:
228
+ """Compute the Kochin function for a list of results and fills a dataset.
229
+
230
+ .. seealso::
231
+ :meth:`~capytaine.post_pro.kochin.compute_kochin`
232
+ The present function is just a wrapper around :code:`compute_kochin`.
233
+ """
234
+ records = pd.DataFrame([
235
+ dict(**result.problem._asdict(), theta=theta, kochin=kochin, kind=result.__class__.__name__)
236
+ for result in results
237
+ for theta, kochin in zip(theta_range.data,
238
+ compute_kochin(result, theta_range, **kwargs))
239
+ ])
240
+
241
+ kochin_data = xr.Dataset()
242
+
243
+ if "RadiationResult" in set(records['kind']):
244
+ radiation = _dataset_from_dataframe(
245
+ records[records['kind'] == "RadiationResult"],
246
+ variables=['kochin'],
247
+ dimensions=['omega', 'radiating_dof', 'theta'],
248
+ optional_dims=['g', 'rho', 'body_name', 'water_depth', 'forward_speed', 'wave_direction']
249
+ )
250
+ kochin_data['kochin'] = radiation['kochin']
251
+
252
+ if "DiffractionResult" in set(records['kind']):
253
+ diffraction = _dataset_from_dataframe(
254
+ records[records['kind'] == "DiffractionResult"],
255
+ ['kochin'],
256
+ dimensions=['omega', 'wave_direction', 'theta'],
257
+ optional_dims=['g', 'rho', 'body_name', 'water_depth', 'forward_speed']
258
+ )
259
+ kochin_data['kochin_diffraction'] = diffraction['kochin']
260
+
261
+ return kochin_data
262
+
263
+
264
+ def collect_records(results):
265
+ records_list = []
266
+ warned_once_about_no_free_surface = False
267
+ for result in results:
268
+ if result.free_surface == np.inf:
269
+ if not warned_once_about_no_free_surface:
270
+ LOG.warning("Datasets currently only support cases with a free surface (free_surface=0.0).\n"
271
+ "Cases without a free surface (free_surface=inf) are ignored.\n"
272
+ "See also https://github.com/mancellin/capytaine/issues/88")
273
+ warned_once_about_no_free_surface = True
274
+ else:
275
+ pass
276
+ else:
277
+ for record in result.records:
278
+ records_list.append(record)
279
+ return records_list
280
+
281
+ def assemble_dataset(results,
282
+ omega=True, wavenumber=True, wavelength=True, period=True,
283
+ mesh=False, hydrostatics=True, attrs=None) -> xr.Dataset:
284
+ """Transform a list of :class:`LinearPotentialFlowResult` into a :class:`xarray.Dataset`.
285
+
286
+ .. todo:: The :code:`mesh` option to store information on the mesh could be improved.
287
+ It could store the full mesh in the dataset to ensure the reproducibility of
288
+ the results.
289
+
290
+ Parameters
291
+ ----------
292
+ results: list of LinearPotentialFlowResult
293
+ The results that will be read.
294
+ omega: bool, optional
295
+ If True, the coordinate 'omega' will be added to the output dataset.
296
+ wavenumber: bool, optional
297
+ If True, the coordinate 'wavenumber' will be added to the output dataset.
298
+ wavelength: bool, optional
299
+ If True, the coordinate 'wavelength' will be added to the output dataset.
300
+ period: bool, optional
301
+ If True, the coordinate 'period' will be added to the output dataset.
302
+ mesh: bool, optional
303
+ If True, store some infos on the mesh in the output dataset.
304
+ hydrostatics: bool, optional
305
+ If True, store the hydrostatic data in the output dataset if they exist.
306
+ attrs: dict, optional
307
+ Attributes that should be added to the output dataset.
308
+ """
309
+ dataset = xr.Dataset()
310
+
311
+ error_msg = 'The first argument of `assemble_dataset` must be either a list of LinearPotentialFlowResult or a bemio.io object'
312
+ if hasattr(results, '__iter__'):
313
+ try:
314
+ if 'capytaine' in results[0].__module__:
315
+ bemio_import = False
316
+ else:
317
+ raise TypeError(error_msg)
318
+ except:
319
+ raise TypeError(error_msg)
320
+
321
+ else:
322
+ try:
323
+ if 'bemio.io' in results.__module__:
324
+ bemio_import = True
325
+ else:
326
+ raise TypeError(error_msg)
327
+ except:
328
+ raise TypeError(error_msg)
329
+
330
+ if bemio_import:
331
+ records = dataframe_from_bemio(results, wavenumber, wavelength) # TODO add hydrostatics
332
+ all_dofs_in_order = {'Surge': None, 'Sway': None, 'Heave': None, 'Roll': None, 'Pitch': None, 'Yaw': None}
333
+ main_freq_type = "omega"
334
+
335
+ else:
336
+ records = pd.DataFrame(collect_records(results))
337
+ all_dofs_in_order = {k: None for r in results for k in r.body.dofs.keys()}
338
+ main_freq_type = Counter((res.provided_freq_type for res in results)).most_common(1)[0][0]
339
+
340
+ if attrs is None:
341
+ attrs = {}
342
+ attrs['creation_of_dataset'] = datetime.now().isoformat()
343
+
344
+ if len(records) == 0:
345
+ raise ValueError("No result passed to assemble_dataset.")
346
+
347
+ inf_dof_cat = pd.CategoricalDtype(categories=all_dofs_in_order.keys())
348
+ records["influenced_dof"] = records["influenced_dof"].astype(inf_dof_cat)
349
+ rad_dof_cat = pd.CategoricalDtype(categories=all_dofs_in_order.keys())
350
+ if 'added_mass' in records.columns:
351
+ records["radiating_dof"] = records["radiating_dof"].astype(rad_dof_cat)
352
+
353
+ optional_dims = ['g', 'rho', 'body_name', 'water_depth', 'forward_speed']
354
+
355
+ # RADIATION RESULTS
356
+ if 'added_mass' in records.columns:
357
+ radiation_cases = _dataset_from_dataframe(
358
+ records,
359
+ variables=['added_mass', 'radiation_damping'],
360
+ dimensions=[main_freq_type, 'radiating_dof', 'influenced_dof'],
361
+ optional_dims=optional_dims + ['wave_direction'])
362
+ radiation_cases.added_mass.attrs['long_name'] = 'Added mass'
363
+ radiation_cases.radiation_damping.attrs['long_name'] = 'Radiation damping'
364
+ radiation_cases.radiating_dof.attrs['long_name'] = 'Radiating DOF'
365
+ radiation_cases.influenced_dof.attrs['long_name'] = 'Influenced DOF'
366
+ dataset = xr.merge([dataset, radiation_cases])
367
+
368
+ # DIFFRACTION RESULTS
369
+ if 'diffraction_force' in records.columns:
370
+ diffraction_cases = _dataset_from_dataframe(
371
+ records,
372
+ variables=['diffraction_force', 'Froude_Krylov_force'],
373
+ dimensions=[main_freq_type, 'wave_direction', 'influenced_dof'],
374
+ optional_dims=optional_dims)
375
+ diffraction_cases.diffraction_force.attrs['long_name'] = 'Diffraction force'
376
+ diffraction_cases.Froude_Krylov_force.attrs['long_name'] = 'Froude Krylov force'
377
+ diffraction_cases.influenced_dof.attrs['long_name'] = 'Influenced DOF'
378
+ diffraction_cases.wave_direction.attrs['long_name'] = 'Wave direction'
379
+ diffraction_cases.wave_direction.attrs['units'] = 'rad'
380
+ dataset = xr.merge([dataset, diffraction_cases])
381
+ dataset['excitation_force'] = dataset['Froude_Krylov_force'] + dataset['diffraction_force']
382
+
383
+ # OTHER FREQUENCIES TYPES
384
+ if omega and main_freq_type != "omega":
385
+ omega_ds = _dataset_from_dataframe(
386
+ records,
387
+ variables=['omega'],
388
+ dimensions=[main_freq_type],
389
+ optional_dims=['g', 'water_depth'] if main_freq_type in {'wavelength', 'wavenumber'} else []
390
+ )
391
+ dataset.coords['omega'] = omega_ds['omega']
392
+ dataset.omega.attrs['long_name'] = 'Angular frequency'
393
+ dataset.omega.attrs['units'] = 'rad/s'
394
+
395
+ if period and main_freq_type != "period":
396
+ period_ds = _dataset_from_dataframe(
397
+ records,
398
+ variables=['period'],
399
+ dimensions=[main_freq_type],
400
+ optional_dims=['g', 'water_depth'] if main_freq_type in {'wavelength', 'wavenumber'} else []
401
+ )
402
+ dataset.coords['period'] = period_ds['period']
403
+ dataset.period.attrs['long_name'] = 'Period'
404
+ dataset.period.attrs['units'] = 's'
405
+
406
+ if wavenumber and main_freq_type != "wavenumber":
407
+ wavenumber_ds = _dataset_from_dataframe(
408
+ records,
409
+ variables=['wavenumber'],
410
+ dimensions=[main_freq_type],
411
+ optional_dims=['g', 'water_depth'] if main_freq_type in {'period', 'omega'} else []
412
+ )
413
+ dataset.coords['wavenumber'] = wavenumber_ds['wavenumber']
414
+ dataset.wavenumber.attrs['long_name'] = 'Angular wavenumber'
415
+ dataset.wavenumber.attrs['units'] = 'rad/m'
416
+
417
+ if wavelength and main_freq_type != "wavelength":
418
+ wavelength_ds = _dataset_from_dataframe(
419
+ records,
420
+ variables=['wavelength'],
421
+ dimensions=[main_freq_type],
422
+ optional_dims=['g', 'water_depth'] if main_freq_type in {'period', 'omega'} else []
423
+ )
424
+ dataset.coords['wavelength'] = wavelength_ds['wavelength']
425
+ dataset.wavelength.attrs['long_name'] = 'Wave length'
426
+ dataset.wavelength.attrs['units'] = 'm'
427
+
428
+ if not all(records["forward_speed"] == 0.0):
429
+ omegae_ds = _dataset_from_dataframe(
430
+ records,
431
+ variables=['encounter_omega'],
432
+ dimensions=['forward_speed', 'wave_direction', main_freq_type],
433
+ optional_dims=['g', 'water_depth'],
434
+ )
435
+ dataset.coords['encounter_omega'] = omegae_ds['encounter_omega']
436
+ dataset.encounter_omega.attrs['long_name'] = 'Encounter angular frequency'
437
+ dataset.encounter_omega.attrs['units'] = 'rad/s'
438
+
439
+ encounter_wave_direction_ds = _dataset_from_dataframe(
440
+ records,
441
+ variables=['encounter_wave_direction'],
442
+ dimensions=['forward_speed', 'wave_direction', main_freq_type],
443
+ optional_dims=[],
444
+ )
445
+ dataset.coords['encounter_wave_direction'] = encounter_wave_direction_ds['encounter_wave_direction']
446
+ dataset.encounter_wave_direction.attrs['long_name'] = 'Encounter wave direction'
447
+ dataset.encounter_wave_direction.attrs['units'] = 'rad'
448
+
449
+ if mesh:
450
+ if bemio_import:
451
+ LOG.warning('Bemio data does not include mesh data. mesh=True is ignored.')
452
+ else:
453
+ # TODO: Store full mesh...
454
+ bodies = list({result.body for result in results}) # Filter out duplicate bodies in the list of results
455
+ nb_faces = {body.name: body.mesh.nb_faces for body in bodies}
456
+
457
+ def name_or_str(c):
458
+ return c.name if hasattr(c, 'name') else str(c)
459
+ quad_methods = {body.name: name_or_str(body.mesh.quadrature_method) for body in bodies}
460
+
461
+ if len(nb_faces) > 1:
462
+ dataset.coords['nb_faces'] = ('body_name', [nb_faces[name] for name in dataset.coords['body_name'].data])
463
+ dataset.coords['quadrature_method'] = ('body_name', [quad_methods[name] for name in dataset.coords['body_name'].data])
464
+ else:
465
+ def the_only(d):
466
+ """Return the only element of a 1-element dictionary"""
467
+ return next(iter(d.values()))
468
+ dataset.coords['nb_faces'] = the_only(nb_faces)
469
+ dataset.coords['quadrature_method'] = the_only(quad_methods)
470
+
471
+ # HYDROSTATICS
472
+ if hydrostatics:
473
+ if bemio_import:
474
+ LOG.warning('Bemio data import being used, hydrostatics=True is ignored.')
475
+ else:
476
+ bodies = list({result.body for result in results})
477
+ dataset = xr.merge([dataset, hydrostatics_dataset(bodies)])
478
+
479
+ dataset.attrs.update(attrs)
480
+ dataset.attrs['capytaine_version'] = __version__
481
+ return dataset
482
+
483
+
484
+ ################################
485
+ # Handling of complex values #
486
+ ################################
487
+
488
+ def separate_complex_values(ds: xr.Dataset) -> xr.Dataset:
489
+ """Return a new Dataset where complex-valued arrays of shape (...)
490
+ have been replaced by real-valued arrays of shape (2, ...).
491
+
492
+ .. seealso::
493
+ :func:`merge_complex_values`
494
+ The invert operation
495
+ """
496
+ ds = ds.copy()
497
+ for variable in ds.data_vars:
498
+ if ds[variable].dtype == complex:
499
+ da = ds[variable]
500
+ new_da = xr.DataArray(np.asarray((np.real(da).data, np.imag(da).data)),
501
+ dims=('complex',) + da.dims)
502
+ ds[variable] = new_da
503
+ ds.coords['complex'] = ['re', 'im']
504
+ return ds
505
+
506
+
507
+ def merge_complex_values(ds: xr.Dataset) -> xr.Dataset:
508
+ """Return a new Dataset where real-valued arrays of shape (2, ...)
509
+ have been replaced by complex-valued arrays of shape (...).
510
+
511
+ .. seealso::
512
+ :func:`separate_complex_values`
513
+ The invert operation
514
+ """
515
+ if 'complex' in ds.coords:
516
+ ds = ds.copy()
517
+ for variable in ds.data_vars:
518
+ if 'complex' in ds[variable].coords:
519
+ da = ds[variable]
520
+ new_dims = [d for d in da.dims if d != 'complex']
521
+ new_da = xr.DataArray(da.sel(complex='re').data + 1j*da.sel(complex='im').data, dims=new_dims)
522
+ ds[variable] = new_da
523
+ ds = ds.drop_vars('complex')
524
+ return ds
@@ -0,0 +1,16 @@
1
+ """This module implements several classes describing matrices defined by blocks.
2
+ These matrices can be nested to recursively define Hierarchical matrices.
3
+ """
4
+ # Copyright (C) 2017-2019 Matthieu Ancellin
5
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
6
+
7
+ from capytaine.matrices.block import BlockMatrix
8
+ from capytaine.matrices.block_toeplitz import (
9
+ BlockToeplitzMatrix, BlockSymmetricToeplitzMatrix,
10
+ BlockCirculantMatrix, EvenBlockSymmetricCirculantMatrix, OddBlockSymmetricCirculantMatrix,
11
+ )
12
+ from capytaine.matrices.builders import (
13
+ cut_matrix, random_block_matrix,
14
+ full_like, zeros_like, ones_like, identity_like,
15
+ )
16
+ from capytaine.matrices.low_rank import LowRankMatrix