wawi 0.0.16__tar.gz → 0.0.18__tar.gz

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 (52) hide show
  1. {wawi-0.0.16 → wawi-0.0.18}/PKG-INFO +42 -4
  2. {wawi-0.0.16 → wawi-0.0.18}/README.md +41 -3
  3. {wawi-0.0.16 → wawi-0.0.18}/wawi/__init__.py +1 -1
  4. wawi-0.0.18/wawi/fe.py +381 -0
  5. {wawi-0.0.16 → wawi-0.0.18}/wawi/general.py +538 -70
  6. {wawi-0.0.16 → wawi-0.0.18}/wawi/identification.py +33 -11
  7. {wawi-0.0.16 → wawi-0.0.18}/wawi/io.py +206 -3
  8. {wawi-0.0.16 → wawi-0.0.18}/wawi/modal.py +384 -6
  9. {wawi-0.0.16 → wawi-0.0.18}/wawi/model/_model.py +1 -1
  10. wawi-0.0.18/wawi/plot.py +868 -0
  11. wawi-0.0.18/wawi/prob.py +37 -0
  12. wawi-0.0.18/wawi/random.py +254 -0
  13. wawi-0.0.18/wawi/signal.py +153 -0
  14. wawi-0.0.18/wawi/structural.py +536 -0
  15. {wawi-0.0.16 → wawi-0.0.18}/wawi/time_domain.py +122 -1
  16. wawi-0.0.18/wawi/tools.py +30 -0
  17. wawi-0.0.18/wawi/wave.py +1035 -0
  18. {wawi-0.0.16 → wawi-0.0.18}/wawi/wind.py +775 -32
  19. wawi-0.0.18/wawi/wind_code.py +40 -0
  20. {wawi-0.0.16 → wawi-0.0.18}/wawi.egg-info/PKG-INFO +42 -4
  21. wawi-0.0.16/wawi/fe.py +0 -134
  22. wawi-0.0.16/wawi/plot.py +0 -572
  23. wawi-0.0.16/wawi/prob.py +0 -9
  24. wawi-0.0.16/wawi/random.py +0 -38
  25. wawi-0.0.16/wawi/signal.py +0 -45
  26. wawi-0.0.16/wawi/structural.py +0 -278
  27. wawi-0.0.16/wawi/tools.py +0 -7
  28. wawi-0.0.16/wawi/wave.py +0 -490
  29. wawi-0.0.16/wawi/wind_code.py +0 -14
  30. {wawi-0.0.16 → wawi-0.0.18}/LICENSE +0 -0
  31. {wawi-0.0.16 → wawi-0.0.18}/examples/3 Software interfacing/Abaqus model export/bergsoysund-export.py +0 -0
  32. {wawi-0.0.16 → wawi-0.0.18}/examples/3 Software interfacing/Abaqus model export/hardanger-export.py +0 -0
  33. {wawi-0.0.16 → wawi-0.0.18}/pyproject.toml +0 -0
  34. {wawi-0.0.16 → wawi-0.0.18}/setup.cfg +0 -0
  35. {wawi-0.0.16 → wawi-0.0.18}/tests/test_IABSE_step11a.py +0 -0
  36. {wawi-0.0.16 → wawi-0.0.18}/tests/test_IABSE_step11c.py +0 -0
  37. {wawi-0.0.16 → wawi-0.0.18}/tests/test_IABSE_step2a.py +0 -0
  38. {wawi-0.0.16 → wawi-0.0.18}/tests/test_wind.py +0 -0
  39. {wawi-0.0.16 → wawi-0.0.18}/wawi/ext/__init__.py +0 -0
  40. {wawi-0.0.16 → wawi-0.0.18}/wawi/ext/abq.py +0 -0
  41. {wawi-0.0.16 → wawi-0.0.18}/wawi/ext/ansys.py +0 -0
  42. {wawi-0.0.16 → wawi-0.0.18}/wawi/ext/orcaflex.py +0 -0
  43. {wawi-0.0.16 → wawi-0.0.18}/wawi/ext/sofistik.py +0 -0
  44. {wawi-0.0.16 → wawi-0.0.18}/wawi/model/__init__.py +0 -0
  45. {wawi-0.0.16 → wawi-0.0.18}/wawi/model/_aero.py +0 -0
  46. {wawi-0.0.16 → wawi-0.0.18}/wawi/model/_dry.py +0 -0
  47. {wawi-0.0.16 → wawi-0.0.18}/wawi/model/_hydro.py +0 -0
  48. {wawi-0.0.16 → wawi-0.0.18}/wawi/model/_screening.py +0 -0
  49. {wawi-0.0.16 → wawi-0.0.18}/wawi.egg-info/SOURCES.txt +0 -0
  50. {wawi-0.0.16 → wawi-0.0.18}/wawi.egg-info/dependency_links.txt +0 -0
  51. {wawi-0.0.16 → wawi-0.0.18}/wawi.egg-info/requires.txt +0 -0
  52. {wawi-0.0.16 → wawi-0.0.18}/wawi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wawi
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: WAve and WInd response prediction
5
5
  Author-email: "Knut A. Kvåle" <knut.a.kvale@ntnu.no>, Ole Øiseth <ole.oiseth@ntnu.no>, Aksel Fenerci <aksel.fenerci@ntnu.no>, Øivind Wiig Petersen <oyvind.w.petersen@ntnu.no>
6
6
  License: MIT License
@@ -92,17 +92,51 @@ pip install git+https://www.github.com/knutankv/wawi.git@main
92
92
  How does WAWI work?
93
93
  ======================
94
94
  By representing both aerodynamic and hydrodynamic motion-induced forces and excitation using a coordinate basis defined by the dry in-vacuum mode shapes of the structure, WAWI is able to versatily predict response based on input from any commercial FE software. The main structure used for response prediction is given in this figure:
95
+
95
96
  ![Model](https://raw.githubusercontent.com/knutankv/wawi/main/docs/flowchart.svg)
96
97
 
97
98
  The object structure of a WAWI model is given here:
98
- ![Modelattributes](https://raw.githubusercontent.com/knutankv/wawi/main/docs/structure.svg)
99
+
100
+ ![Model attributes](https://raw.githubusercontent.com/knutankv/wawi/main/docs/structure.svg)
99
101
 
100
102
  Further details regarding hydrodynamic definitions initiated by the `Hydro` class is given below:
103
+
101
104
  ![Hydro](https://raw.githubusercontent.com/knutankv/wawi/main/docs/hydro_part.svg)
102
105
 
103
106
  Further details regarding aerodynamic definitions initiated by the `Aero` class is given below:
107
+
104
108
  ![Aero](https://raw.githubusercontent.com/knutankv/wawi/main/docs/aero_part.svg)
105
109
 
110
+ Wave conditions
111
+ ----------------------
112
+ The wave field definition is based on the assumption that the two-dimensional wave spectral density can be decomposed into a directional distribution and a one-dimensional wave spectral density, i.e., $S_\eta(\omega,\theta) = D(\theta) S(\omega)$. The two factors are defined using these well-known formulations:
113
+
114
+ * $S(\omega)$: JONSWAP spectrum (see [Hasselmann et al., 1973](https://pure.mpg.de/pubman/faces/ViewItemOverviewPage.jsp?itemId=item_3262854))
115
+ * $D(\theta)$: cos-2s directional distribution (see Longuet-Higgins et al., 1963)
116
+
117
+ Currents are defined by a homogeneous current speed `U` and corresponding direction `thetaU`.
118
+
119
+ An example of a two-dimensional wave spectral density based on (arbitrarily chosen) parameters $H_s = 2.1$ m, $T_p = 2.1$ s, $\gamma = 4.0$, $s = 10$ and $\theta_0 = 75^\circ$ is shown in this plot:
120
+
121
+ ![2D wave PSD](https://raw.githubusercontent.com/knutankv/wawi/main/docs/wave_S2d.png)
122
+
123
+ It is noted that you can easily assign custom functions of the `S` and `D` of the seastate (or customly on all pontoons for full control) instead of relying on the built in JONSWAP and cos-2s definitions.
124
+
125
+ Wind conditions
126
+ ----------------------
127
+ The wind field is defined by single-point turbulence wind spectra (for all turbulence components $u$, $v$ and $w$) and coherence definitions.
128
+
129
+ By default, wind spectra can be defined using these two definitions:
130
+
131
+ * Kaimal spectrum defined by length scale parameters ($L^x_u$, $L^x_v$, $L^x_w$), spectral shape parameters ($A_u$, $A_v$ and $A_w$) and turbulence intensities ($I_u$, $I_v$ and $I_w$); see [Kaimal et al., 1972](https://www.climatexchange.nl/projects/alteddy/papers/Kaimal-1972.pdf)
132
+ * von Karmán spectrum defined by only length scale parameters ($L^x_u$, $L^x_v$, $L^x_w$) and turbulence intensities ($I_u=\sigma_u/U$, $I_v=\sigma_v/U$ and $I_w=\sigma_w/U$); see [von Kármán, 1948](http://dx.doi.org/10.1073/pnas.34.11.530)
133
+
134
+ Furthermore, the coherence of the wind field is defined by the nine decay parameters $C_{ux}$, $C_{vx}$, $C_{wx}$, $C_{uy}$, $C_{vy}$, $C_{wy}$, $C_{uz}$ $C_{vz}$, $C_{wz}$; see e.g. [Simiu and Scanlan, 1996](https://library.wur.nl/WebQuery/titel/1606468) for details.
135
+
136
+ An example of turbulence spectral densities based on (arbitrarily chosen) parameters $I_u=0.136$, $I_v=0.0$, $I_w=0.072$, $L^x_u=115$, $L^x_w=9.58$, $A_u=6.8$ (only relevant for Kaimal-type) and $A_w=9.4$ (only relevant for Kaimal-type) is given below:
137
+
138
+ ![1D turbulence PSD](https://raw.githubusercontent.com/knutankv/wawi/main/docs/wind_psd.png)
139
+
106
140
 
107
141
  Quick start
108
142
  =======================
@@ -181,16 +215,20 @@ References
181
215
  =======================
182
216
  The following papers provide background for the implementation:
183
217
 
184
- * Beam (FE) description of aerodynamic forces: [Øiseth et al. (2012)](https://www.sciencedirect.com/science/article/abs/pii/S0168874X11001880)
185
218
  * Wave modelling and response prediction: [Kvåle et al. (2016)](https://www.sciencedirect.com/science/article/abs/pii/S004579491500334X)
186
219
  * Inhomogeneous wave modelling: [Kvåle et al. (2024)](https://www.sciencedirect.com/science/article/pii/S0141118723003437)
187
220
  * Hydrodynamic interaction effects: [Fenerci et al. (2022)](https://www.sciencedirect.com/science/article/pii/S095183392200017X)
221
+ * Beam (FE) description of aerodynamic forces: [Øiseth et al. (2012)](https://www.sciencedirect.com/science/article/abs/pii/S0168874X11001880)
188
222
  * Wave-current interaction: [Fredriksen et al. (2024)](https://www.researchgate.net/profile/Arnt-Fredriksen/publication/386453916_On_the_wave-current_interaction_effect_on_linear_motion_for_floating_bridges/links/6751a40fabddbb448c65cbef/On-the-wave-current-interaction-effect-on-linear-motion-for-floating-bridges.pdf)
189
223
 
190
224
 
191
225
  Citation
192
226
  =======================
193
- Zenodo research entry: [![DOI](https://zenodo.org/badge/921621297.svg)](https://doi.org/10.5281/zenodo.14895014)
227
+ Please cite the use of this software as follows:
228
+
229
+ Kvåle, K. A., Fenerci, A., Petersen, Ø. W., & Øiseth, O. A. (2025). WAWI. Zenodo. https://doi.org/10.5281/zenodo.15482552
230
+ [![DOI](https://zenodo.org/badge/921621297.svg)](https://doi.org/10.5281/zenodo.14895014)
231
+
194
232
 
195
233
  Support
196
234
  =======================
@@ -45,17 +45,51 @@ pip install git+https://www.github.com/knutankv/wawi.git@main
45
45
  How does WAWI work?
46
46
  ======================
47
47
  By representing both aerodynamic and hydrodynamic motion-induced forces and excitation using a coordinate basis defined by the dry in-vacuum mode shapes of the structure, WAWI is able to versatily predict response based on input from any commercial FE software. The main structure used for response prediction is given in this figure:
48
+
48
49
  ![Model](https://raw.githubusercontent.com/knutankv/wawi/main/docs/flowchart.svg)
49
50
 
50
51
  The object structure of a WAWI model is given here:
51
- ![Modelattributes](https://raw.githubusercontent.com/knutankv/wawi/main/docs/structure.svg)
52
+
53
+ ![Model attributes](https://raw.githubusercontent.com/knutankv/wawi/main/docs/structure.svg)
52
54
 
53
55
  Further details regarding hydrodynamic definitions initiated by the `Hydro` class is given below:
56
+
54
57
  ![Hydro](https://raw.githubusercontent.com/knutankv/wawi/main/docs/hydro_part.svg)
55
58
 
56
59
  Further details regarding aerodynamic definitions initiated by the `Aero` class is given below:
60
+
57
61
  ![Aero](https://raw.githubusercontent.com/knutankv/wawi/main/docs/aero_part.svg)
58
62
 
63
+ Wave conditions
64
+ ----------------------
65
+ The wave field definition is based on the assumption that the two-dimensional wave spectral density can be decomposed into a directional distribution and a one-dimensional wave spectral density, i.e., $S_\eta(\omega,\theta) = D(\theta) S(\omega)$. The two factors are defined using these well-known formulations:
66
+
67
+ * $S(\omega)$: JONSWAP spectrum (see [Hasselmann et al., 1973](https://pure.mpg.de/pubman/faces/ViewItemOverviewPage.jsp?itemId=item_3262854))
68
+ * $D(\theta)$: cos-2s directional distribution (see Longuet-Higgins et al., 1963)
69
+
70
+ Currents are defined by a homogeneous current speed `U` and corresponding direction `thetaU`.
71
+
72
+ An example of a two-dimensional wave spectral density based on (arbitrarily chosen) parameters $H_s = 2.1$ m, $T_p = 2.1$ s, $\gamma = 4.0$, $s = 10$ and $\theta_0 = 75^\circ$ is shown in this plot:
73
+
74
+ ![2D wave PSD](https://raw.githubusercontent.com/knutankv/wawi/main/docs/wave_S2d.png)
75
+
76
+ It is noted that you can easily assign custom functions of the `S` and `D` of the seastate (or customly on all pontoons for full control) instead of relying on the built in JONSWAP and cos-2s definitions.
77
+
78
+ Wind conditions
79
+ ----------------------
80
+ The wind field is defined by single-point turbulence wind spectra (for all turbulence components $u$, $v$ and $w$) and coherence definitions.
81
+
82
+ By default, wind spectra can be defined using these two definitions:
83
+
84
+ * Kaimal spectrum defined by length scale parameters ($L^x_u$, $L^x_v$, $L^x_w$), spectral shape parameters ($A_u$, $A_v$ and $A_w$) and turbulence intensities ($I_u$, $I_v$ and $I_w$); see [Kaimal et al., 1972](https://www.climatexchange.nl/projects/alteddy/papers/Kaimal-1972.pdf)
85
+ * von Karmán spectrum defined by only length scale parameters ($L^x_u$, $L^x_v$, $L^x_w$) and turbulence intensities ($I_u=\sigma_u/U$, $I_v=\sigma_v/U$ and $I_w=\sigma_w/U$); see [von Kármán, 1948](http://dx.doi.org/10.1073/pnas.34.11.530)
86
+
87
+ Furthermore, the coherence of the wind field is defined by the nine decay parameters $C_{ux}$, $C_{vx}$, $C_{wx}$, $C_{uy}$, $C_{vy}$, $C_{wy}$, $C_{uz}$ $C_{vz}$, $C_{wz}$; see e.g. [Simiu and Scanlan, 1996](https://library.wur.nl/WebQuery/titel/1606468) for details.
88
+
89
+ An example of turbulence spectral densities based on (arbitrarily chosen) parameters $I_u=0.136$, $I_v=0.0$, $I_w=0.072$, $L^x_u=115$, $L^x_w=9.58$, $A_u=6.8$ (only relevant for Kaimal-type) and $A_w=9.4$ (only relevant for Kaimal-type) is given below:
90
+
91
+ ![1D turbulence PSD](https://raw.githubusercontent.com/knutankv/wawi/main/docs/wind_psd.png)
92
+
59
93
 
60
94
  Quick start
61
95
  =======================
@@ -134,16 +168,20 @@ References
134
168
  =======================
135
169
  The following papers provide background for the implementation:
136
170
 
137
- * Beam (FE) description of aerodynamic forces: [Øiseth et al. (2012)](https://www.sciencedirect.com/science/article/abs/pii/S0168874X11001880)
138
171
  * Wave modelling and response prediction: [Kvåle et al. (2016)](https://www.sciencedirect.com/science/article/abs/pii/S004579491500334X)
139
172
  * Inhomogeneous wave modelling: [Kvåle et al. (2024)](https://www.sciencedirect.com/science/article/pii/S0141118723003437)
140
173
  * Hydrodynamic interaction effects: [Fenerci et al. (2022)](https://www.sciencedirect.com/science/article/pii/S095183392200017X)
174
+ * Beam (FE) description of aerodynamic forces: [Øiseth et al. (2012)](https://www.sciencedirect.com/science/article/abs/pii/S0168874X11001880)
141
175
  * Wave-current interaction: [Fredriksen et al. (2024)](https://www.researchgate.net/profile/Arnt-Fredriksen/publication/386453916_On_the_wave-current_interaction_effect_on_linear_motion_for_floating_bridges/links/6751a40fabddbb448c65cbef/On-the-wave-current-interaction-effect-on-linear-motion-for-floating-bridges.pdf)
142
176
 
143
177
 
144
178
  Citation
145
179
  =======================
146
- Zenodo research entry: [![DOI](https://zenodo.org/badge/921621297.svg)](https://doi.org/10.5281/zenodo.14895014)
180
+ Please cite the use of this software as follows:
181
+
182
+ Kvåle, K. A., Fenerci, A., Petersen, Ø. W., & Øiseth, O. A. (2025). WAWI. Zenodo. https://doi.org/10.5281/zenodo.15482552
183
+ [![DOI](https://zenodo.org/badge/921621297.svg)](https://doi.org/10.5281/zenodo.14895014)
184
+
147
185
 
148
186
  Support
149
187
  =======================
@@ -3,7 +3,7 @@
3
3
  '''
4
4
  __pdoc__ = {'wawi.ext.abq': False}
5
5
 
6
- __version__ = "0.0.16"
6
+ __version__ = "0.0.18"
7
7
 
8
8
  # Other packages
9
9
  import numpy as np
wawi-0.0.18/wawi/fe.py ADDED
@@ -0,0 +1,381 @@
1
+ # -*- coding: utf-8 -*-
2
+ import numpy as np
3
+ from .general import blkdiag, transform_unit
4
+
5
+ '''
6
+ FE-related tools.
7
+ '''
8
+
9
+ def intpoints_from_elements(nodes, elements, sort_axis=0):
10
+ """
11
+ Calculates the integration points (midpoints) for each element based on node coordinates.
12
+
13
+ Parameters
14
+ ----------
15
+ nodes : np.ndarray
16
+ Array of node coordinates with shape (n_nodes, 4), where columns represent node index and x, y, z coordinates.
17
+ elements : np.ndarray
18
+ Array of element connectivity with shape (n_elements, 2), where each row contains indices of the two nodes forming an element.
19
+ sort_axis : int, optional
20
+ Axis (0 for x, 1 for y, 2 for z) to sort the integration points by. Default is 0 (x-axis).
21
+
22
+ Returns
23
+ -------
24
+ x : np.ndarray
25
+ Array of x-coordinates of the integration points, sorted by the specified axis.
26
+ y : np.ndarray
27
+ Array of y-coordinates of the integration points, sorted by the specified axis.
28
+ z : np.ndarray
29
+ Array of z-coordinates of the integration points, sorted by the specified axis.
30
+
31
+ Notes
32
+ -----
33
+ Assumes that `nodeix_from_elements` is a function that returns the indices of the nodes for each element. Docstring is generated by Github Copilot.
34
+ """
35
+
36
+ nodeix = nodeix_from_elements(elements, nodes).astype('int')
37
+
38
+ intpoints = (nodes[nodeix[:,0], 1:4]+nodes[nodeix[:,1], 1:4])/2
39
+ sortix = np.argsort(intpoints[:,sort_axis])
40
+ intpoints = intpoints[sortix, :]
41
+
42
+ x = intpoints[:, 0]
43
+ y = intpoints[:, 1]
44
+ z = intpoints[:, 2]
45
+
46
+ return x, y, z
47
+
48
+
49
+ def nodeix_from_elements(element_matrix, node_matrix, return_as_flat=False):
50
+ """
51
+ Map element node labels to their corresponding indices in the node matrix.
52
+
53
+ Parameters
54
+ ----------
55
+ element_matrix : np.ndarray
56
+ Array of elements with shape (n_elements, m), where columns 1 and 2 contain node IDs for each element.
57
+ node_matrix : np.ndarray
58
+ Array of nodes with shape (n_nodes, k), where column 0 contains node IDs.
59
+ return_as_flat : bool, optional
60
+ If True, returns a 1D array of unique node indices used by all elements.
61
+ If False, returns a 2D array of shape (n_elements, 2) with node indices for each element.
62
+ Default is False.
63
+
64
+ Returns
65
+ -------
66
+ np.ndarray
67
+ If return_as_flat is False, returns a 2D array of node indices for each element (shape: n_elements, 2).
68
+ If return_as_flat is True, returns a 1D array of unique node indices.
69
+
70
+ Notes
71
+ -----
72
+ Each element is assumed to be defined by two node labels in columns 1 and 2 of element_matrix. Docstring is generated by GitHub Copilot.
73
+ """
74
+ nodeix = [None] * len(element_matrix[:, 0])
75
+ for element_ix, __ in enumerate(element_matrix[:, 0]):
76
+ node1 = element_matrix[element_ix, 1]
77
+ node2 = element_matrix[element_ix, 2]
78
+
79
+ nodeix1 = np.where(node_matrix[:, 0] == node1)[0][0]
80
+ nodeix2 = np.where(node_matrix[:, 0] == node2)[0][0]
81
+ nodeix[element_ix] = [nodeix1, nodeix2]
82
+
83
+ nodeix = np.array(nodeix)
84
+
85
+ if return_as_flat:
86
+ nodeix = np.unique(nodeix.flatten())
87
+
88
+ return nodeix
89
+
90
+
91
+ def create_node_dict(element_dict, node_labels, x_nodes, y_nodes, z_nodes):
92
+ """
93
+ Creates a dictionary mapping element keys to their corresponding node data.
94
+
95
+ Parameters
96
+ ----------
97
+ element_dict : dict
98
+ A dictionary where each key corresponds to an element and each value contains information
99
+ about the nodes associated with that element.
100
+ node_labels : array-like
101
+ An array of node labels/IDs.
102
+ x_nodes : array-like
103
+ An array of x-coordinates for each node.
104
+ y_nodes : array-like
105
+ An array of y-coordinates for each node.
106
+ z_nodes : array-like
107
+ An array of z-coordinates for each node.
108
+
109
+ Returns
110
+ -------
111
+ node_dict : dict
112
+ A dictionary where each key matches an element key from `element_dict`, and each value is
113
+ an array containing the node label and coordinates (label, x, y, z) for the nodes
114
+ associated with that element.
115
+
116
+ Notes
117
+ -----
118
+ This function relies on the helper function `nodeix_from_elements` to determine the indices
119
+ of nodes associated with each element. Docstring is generated by GitHub Copilot.
120
+ """
121
+
122
+ node_dict = dict()
123
+ node_matrix = np.vstack([node_labels, x_nodes, y_nodes, z_nodes]).T
124
+
125
+ for key in element_dict.keys():
126
+ node_ix = nodeix_from_elements(element_dict[key], node_matrix, return_as_flat=True)
127
+ node_dict[key] = node_matrix[node_ix, :]
128
+
129
+ return node_dict
130
+
131
+
132
+ def elements_with_node(element_matrix, node_label):
133
+ """
134
+ Finds elements containing a specific node and returns their labels, indices, and local node indices.
135
+
136
+ Parameters
137
+ ----------
138
+ element_matrix : np.ndarray
139
+ A 2D array where each row represents an element. The first column contains element labels,
140
+ and the second and third columns contain node labels associated with each element.
141
+ node_label : int or float
142
+ The node label to search for within the element matrix.
143
+
144
+ Returns
145
+ -------
146
+ element_labels : np.ndarray
147
+ Array of element labels that contain the specified node.
148
+ element_ixs : np.ndarray
149
+ Array of indices in `element_matrix` where the specified node is found.
150
+ local_node_ix : np.ndarray
151
+ Array indicating the local node index (0 or 1) within each element where the node is found.
152
+
153
+ Examples
154
+ --------
155
+ >>> element_matrix = np.array([[1, 10, 20],
156
+ ... [2, 20, 30],
157
+ ... [3, 10, 30]])
158
+ >>> elements_with_node(element_matrix, 10)
159
+ (array([1, 3]), array([0, 2]), array([0., 0.]))
160
+
161
+ Notes
162
+ ---------
163
+ Docstring is generated by GitHub Copilot.
164
+ """
165
+ element_ixs1 = np.array(np.where(element_matrix[:,1]==node_label)).flatten()
166
+ element_ixs2 = np.array(np.where(element_matrix[:,2]==node_label)).flatten()
167
+
168
+ element_ixs = np.hstack([element_ixs1, element_ixs2])
169
+ element_labels = element_matrix[element_ixs, 0]
170
+
171
+ local_node_ix = np.zeros(np.shape(element_ixs))
172
+ local_node_ix[np.arange(0,len(element_ixs1))] = 0
173
+ local_node_ix[np.arange(len(element_ixs1), len(element_ixs1) + len(element_ixs2))] = 1
174
+
175
+ return element_labels, element_ixs, local_node_ix
176
+
177
+
178
+ def nodes_to_beam_element_matrix(node_labels, first_element_label=1):
179
+ """
180
+ Generates an element connectivity matrix for beam elements from a sequence of node labels.
181
+
182
+ Parameters
183
+ ----------
184
+ node_labels : array_like
185
+ Sequence of node labels (integers or floats) defining the order of nodes along the beam.
186
+ first_element_label : int, optional
187
+ The label to assign to the first element. Default is 1.
188
+
189
+ Returns
190
+ -------
191
+ element_matrix : ndarray of shape (n_elements, 3)
192
+ Array where each row represents a beam element. The columns are:
193
+ [element_label, start_node_label, end_node_label].
194
+
195
+ Examples
196
+ --------
197
+ >>> nodes = [10, 20, 30, 40]
198
+ >>> nodes_to_beam_element_matrix(nodes)
199
+ array([[ 1., 10., 20.],
200
+ [ 2., 20., 30.],
201
+ [ 3., 30., 40.]])
202
+
203
+ Notes
204
+ ---------
205
+ Docstring is generated by GitHub Copilot.
206
+ """
207
+
208
+ n_nodes = len(node_labels)
209
+ n_elements = n_nodes-1
210
+
211
+ element_matrix = np.empty([n_elements, 3])
212
+ element_matrix[:, 0] = np.arange(first_element_label,first_element_label+n_elements)
213
+ element_matrix[:, 1] = node_labels[0:-1]
214
+ element_matrix[:, 2] = node_labels[1:]
215
+
216
+ return element_matrix
217
+
218
+
219
+ def node_ix_to_dof_ix(node_ix, n_dofs=6):
220
+ """
221
+ Converts a node index to a degree of freedom (DOF) index.
222
+ Each node has n_dofs degrees of freedom, and this function returns the corresponding DOF indices.
223
+
224
+ Parameters
225
+ ----------
226
+ node_ix : int
227
+ Index of the node for which to find the DOF indices.
228
+ n_dofs : int, optional
229
+ Number of degrees of freedom per node. Default is 6.
230
+
231
+ Returns
232
+ -------
233
+ dof_ix : np.ndarray
234
+ Array of DOF indices corresponding to the given node index.
235
+
236
+ Examples
237
+ --------
238
+ >>> node_ix = 2
239
+ >>> n_dofs = 6
240
+ >>> dof_ix = node_ix_to_dof_ix(node_ix, n_dofs)
241
+ >>> print(dof_ix)
242
+ [12 13 14 15 16 17]
243
+
244
+ Notes
245
+ --------
246
+ Docstring is generated by GitHub Copilot.
247
+
248
+ """
249
+ start = node_ix*n_dofs
250
+ stop = node_ix*n_dofs+n_dofs
251
+ dof_ix = []
252
+ for (i,j) in zip(start,stop):
253
+ dof_ix.append(np.arange(i,j))
254
+
255
+ dof_ix = np.array(dof_ix).flatten()
256
+
257
+ return dof_ix
258
+
259
+
260
+ def dof_sel(arr, dof_sel, n_dofs=6, axis=0):
261
+ """
262
+ Selects degrees of freedom (DOFs) from an array along a specified axis.
263
+
264
+ Parameters
265
+ ----------
266
+ arr : np.ndarray
267
+ Input array from which to select DOFs.
268
+ dof_sel : array-like
269
+ Indices of the DOFs to select (relative to each block of n_dofs).
270
+ n_dofs : int, optional
271
+ Number of DOFs per node or block. Default is 6.
272
+ axis : int, optional
273
+ Axis along which to select DOFs. Default is 0.
274
+
275
+ Returns
276
+ -------
277
+ arr_sel : np.ndarray
278
+ Array containing only the selected DOFs along the specified axis.
279
+
280
+ Examples
281
+ --------
282
+ >>> arr = np.arange(18).reshape(3, 6)
283
+ >>> dof_sel(arr, [0, 2], n_dofs=6, axis=1)
284
+ array([[ 0, 2],
285
+ [ 6, 8],
286
+ [12, 14]])
287
+
288
+ Notes
289
+ -------
290
+ Docstring is generated by GitHub Copilot.
291
+ """
292
+
293
+ N = np.shape(arr)[axis]
294
+ all_ix = [range(dof_sel_i, N, n_dofs) for dof_sel_i in dof_sel]
295
+ sel_ix = np.array(all_ix).T.flatten()
296
+
297
+ # Select the elements
298
+ arr_sel = np.take(arr, sel_ix, axis=axis)
299
+
300
+ return arr_sel
301
+
302
+
303
+
304
+ def elements_from_common_nodes(element_matrix, selected_nodes):
305
+ """
306
+ Find elements that have both nodes in `selected_nodes`.
307
+
308
+ Parameters
309
+ ----------
310
+ element_matrix : np.ndarray
311
+ Array of elements with shape (n_elements, 3), where columns are [element_id, node1, node2].
312
+ selected_nodes : array-like
313
+ List or array of node labels to search for.
314
+
315
+ Returns
316
+ -------
317
+ selected_element_matrix : np.ndarray
318
+ Subset of `element_matrix` where both nodes are in `selected_nodes`.
319
+ sel_ix : np.ndarray
320
+ Indices of the selected elements in the original `element_matrix`.
321
+
322
+ Notes
323
+ -----
324
+ Only elements where both node1 and node2 are in `selected_nodes` are selected. Docstring is generated by GitHub Copilot.
325
+ """
326
+
327
+ mask = np.isin(element_matrix[:, 1], selected_nodes) & np.isin(element_matrix[:, 2], selected_nodes)
328
+ sel_ix = np.where(mask)[0]
329
+ selected_element_matrix = element_matrix[sel_ix, :]
330
+
331
+ return selected_element_matrix, sel_ix
332
+
333
+
334
+ def transform_elements(node_matrix, element_matrix, e2p, repeats=1):
335
+ """
336
+ Transforms elements from global to local coordinates.
337
+ Given a node matrix and an element matrix, this function computes the transformation matrices
338
+ from global to local coordinates for each element. The transformation is based on the direction
339
+ vector between the nodes of each element. The transformation matrices are constructed using
340
+ the `transform_unit` function and are repeated as specified.
341
+
342
+ Parameters
343
+ ----------
344
+ node_matrix : np.ndarray
345
+ Array of node data. The first column should contain node IDs, and the remaining columns
346
+ should contain node coordinates.
347
+ element_matrix : np.ndarray
348
+ Array of element data. Each row corresponds to an element, with the first column as the
349
+ element ID and the next columns as node IDs defining the element.
350
+ e2p : np.ndarray or similar
351
+ Additional parameter passed to `transform_unit` for transformation construction.
352
+ repeats : int, optional
353
+ Number of times to repeat the transformation block (default is 1).
354
+
355
+ Returns
356
+ -------
357
+ list of np.ndarray
358
+ List of transformation matrices, one for each element, mapping global to local coordinates.
359
+
360
+ Notes
361
+ -----
362
+ - Assumes that `transform_unit` and `blkdiag` functions are defined elsewhere.
363
+ - The function matches node IDs between `element_matrix` and `node_matrix` to determine node positions.
364
+ Docstring is generated by GitHub Copilot.
365
+ """
366
+
367
+ n_elements = np.shape(element_matrix)[0]
368
+ T_g2el = [None]*n_elements
369
+
370
+ for el in range(0, n_elements):
371
+ n1_ix = np.where(node_matrix[:,0]==element_matrix[el, 1])
372
+ n2_ix = np.where(node_matrix[:,0]==element_matrix[el, 2])
373
+
374
+ X1 = node_matrix[n1_ix, 1:]
375
+ X2 = node_matrix[n2_ix, 1:]
376
+ dx = X2-X1
377
+ e1 = dx/np.linalg.norm(dx)
378
+
379
+ T_g2el[el] = blkdiag(transform_unit(e1, e2p), repeats) # Transform from global to local coordinates VLocal = T*VGlobal
380
+
381
+ return T_g2el