flapjax-full 1.2.0.dev0__py3-none-any.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.
- flapjax/__init__.py +0 -0
- flapjax/aero/__init__.py +19 -0
- flapjax/aero/aic.py +310 -0
- flapjax/aero/data_structures.py +689 -0
- flapjax/aero/flowfields.py +200 -0
- flapjax/aero/gradients/__init__.py +0 -0
- flapjax/aero/gradients/data_structures.py +397 -0
- flapjax/aero/linear/__init__.py +2 -0
- flapjax/aero/linear/data_structures.py +166 -0
- flapjax/aero/linear/linear_uvlm.py +597 -0
- flapjax/aero/utils.py +722 -0
- flapjax/aero/uvlm.py +2462 -0
- flapjax/algebra/__init__.py +0 -0
- flapjax/algebra/array_utils.py +445 -0
- flapjax/algebra/base.py +745 -0
- flapjax/algebra/integration.py +81 -0
- flapjax/algebra/se3.py +395 -0
- flapjax/algebra/so3.py +212 -0
- flapjax/algebra/test_routines.py +280 -0
- flapjax/coupled/__init__.py +17 -0
- flapjax/coupled/coupled.py +484 -0
- flapjax/coupled/data_structures.py +441 -0
- flapjax/coupled/gradients/__init__.py +0 -0
- flapjax/coupled/gradients/coupled.py +2027 -0
- flapjax/coupled/gradients/data_structures.py +122 -0
- flapjax/coupled/linear/__init__.py +0 -0
- flapjax/coupled/linear/data_structures.py +36 -0
- flapjax/coupled/linear/linear_coupled.py +1051 -0
- flapjax/models/__init__.py +0 -0
- flapjax/models/cantilever_wing.py +82 -0
- flapjax/models/flying_spaghetti/__init__.py +0 -0
- flapjax/models/flying_spaghetti/flying_spaghetti.py +103 -0
- flapjax/models/flying_spaghetti/free_dynamic.ipynb +1248 -0
- flapjax/models/flying_spaghetti/mass_optimisation.ipynb +437 -0
- flapjax/models/geradin_beam/__init__.py +0 -0
- flapjax/models/geradin_beam/adjoint_gradients.ipynb +296 -0
- flapjax/models/geradin_beam/geradin_beam.py +41 -0
- flapjax/models/geradin_beam/static_deformation.ipynb +361 -0
- flapjax/models/ifasd_aviation_2026/__init__.py +0 -0
- flapjax/models/ifasd_aviation_2026/cantilever_wing_adjoint.py +158 -0
- flapjax/models/ifasd_aviation_2026/optimal_roll.py +360 -0
- flapjax/models/panel_scaling_profile.py +398 -0
- flapjax/models/patil_wing/__init__.py +0 -0
- flapjax/models/patil_wing/open_loop_control.ipynb +722 -0
- flapjax/models/patil_wing/patil_wing.py +127 -0
- flapjax/models/pazy/__init__.py +0 -0
- flapjax/models/pazy/base.py +280 -0
- flapjax/models/pazy/straight/__init__.py +0 -0
- flapjax/models/pazy/straight/data/__init__.py +0 -0
- flapjax/models/pazy/straight/data/prepazy_properties.py +819 -0
- flapjax/models/pazy/straight/data/technion_pazy_properties.py +796 -0
- flapjax/models/pazy/straight/deformed_modes.ipynb +259 -0
- flapjax/models/pazy/straight/flutter.ipynb +431 -0
- flapjax/models/pazy/straight/lco.ipynb +18097 -0
- flapjax/models/pazy/straight/pazy_wing.py +108 -0
- flapjax/models/pazy/straight/static_deflection.ipynb +335 -0
- flapjax/models/pazy/swept/__init__.py +0 -0
- flapjax/models/pazy/swept/data/__init__.py +0 -0
- flapjax/models/pazy/swept/data/properties.py +408 -0
- flapjax/models/pazy/swept/deformed_modes.ipynb +299 -0
- flapjax/models/pazy/swept/flutter.ipynb +474 -0
- flapjax/models/pazy/swept/lco.ipynb +9880 -0
- flapjax/models/pazy/swept/static_deflection.ipynb +477 -0
- flapjax/models/pazy/swept/swept_pazy_wing.py +61 -0
- flapjax/models/scitech_abstract_2027/__init__.py +0 -0
- flapjax/models/scitech_abstract_2027/linear_uvlm_case.py +186 -0
- flapjax/models/simple_hale/__init__.py +0 -0
- flapjax/models/simple_hale/parallel_gust.ipynb +26503 -0
- flapjax/models/simple_hale/simple_hale.py +397 -0
- flapjax/plotting/__init__.py +1 -0
- flapjax/plotting/aerogrid.py +134 -0
- flapjax/plotting/beam.py +271 -0
- flapjax/plotting/modal.py +241 -0
- flapjax/plotting/pvd.py +64 -0
- flapjax/structure/__init__.py +18 -0
- flapjax/structure/beam.py +3049 -0
- flapjax/structure/data_structures.py +658 -0
- flapjax/structure/gradients/__init__.py +0 -0
- flapjax/structure/gradients/beam.py +1363 -0
- flapjax/structure/gradients/data_structures.py +294 -0
- flapjax/structure/linear/__init__.py +0 -0
- flapjax/structure/linear/data_structures.py +59 -0
- flapjax/structure/linear/linear_beam.py +455 -0
- flapjax/structure/time_integration.py +174 -0
- flapjax/structure/utils.py +279 -0
- flapjax/tests/aero/test_infinite_wing_polar.py +130 -0
- flapjax/tests/aero/test_linear_planar_wing.py +380 -0
- flapjax/tests/aero/test_mirrored_wing.py +78 -0
- flapjax/tests/aero/test_static_no_wake.py +65 -0
- flapjax/tests/aero/test_variable_wake_disc.py +89 -0
- flapjax/tests/algebra/test_beam_algebra.py +592 -0
- flapjax/tests/algebra/test_kernels.py +98 -0
- flapjax/tests/conftest.py +16 -0
- flapjax/tests/coupled/dynamic/test_dynamic_equilibrium.py +69 -0
- flapjax/tests/coupled/dynamic/test_dynamic_equilibrium_adjoint.py +132 -0
- flapjax/tests/coupled/dynamic/test_dynamic_gust_adjoint.py +160 -0
- flapjax/tests/coupled/dynamic/test_parallel_cantilever.py +39 -0
- flapjax/tests/coupled/static/test_pazy.py +206 -0
- flapjax/tests/coupled/static/test_static_adjoint.py +127 -0
- flapjax/tests/plotting/test_beam_plot.py +35 -0
- flapjax/tests/structure/dynamic_linear/test_oscillating_cantilever.py +170 -0
- flapjax/tests/structure/dynamic_primal/test_applied_force_point.py +131 -0
- flapjax/tests/structure/dynamic_primal/test_const_initial_linear_velocity.py +91 -0
- flapjax/tests/structure/dynamic_primal/test_const_initial_rotational_velocity.py +186 -0
- flapjax/tests/structure/dynamic_primal/test_const_initial_velocity_lumped_mass.py +94 -0
- flapjax/tests/structure/dynamic_primal/test_gravity_beam_drop.py +113 -0
- flapjax/tests/structure/dynamic_primal/test_gravity_point_drop.py +82 -0
- flapjax/tests/structure/dynamic_primal/test_parallel_spaghetti.py +42 -0
- flapjax/tests/structure/dynamic_primal/test_two_lumped_mass_const_rot_velocity.py +195 -0
- flapjax/tests/structure/dynamic_tangent/test_beam_dynamic_adjoint.py +131 -0
- flapjax/tests/structure/dynamic_tangent/test_lumped_mass_dynamic_adjoint.py +119 -0
- flapjax/tests/structure/static_primal/test_geradin_beam.py +50 -0
- flapjax/tests/structure/static_primal/test_mass_gravity.py +153 -0
- flapjax/tests/structure/static_primal/test_matrices.py +60 -0
- flapjax/tests/structure/static_primal/test_modal.py +91 -0
- flapjax/tests/structure/static_primal/test_multi_element_beam.py +642 -0
- flapjax/tests/structure/static_primal/test_pazy_modes.py +173 -0
- flapjax/tests/structure/static_primal/test_two_node_beam.py +533 -0
- flapjax/tests/structure/static_tangent/test_geradin_gradients.py +111 -0
- flapjax/utils/__init__.py +0 -0
- flapjax/utils/constants.py +23 -0
- flapjax/utils/data_structures.py +435 -0
- flapjax/utils/linear.py +857 -0
- flapjax/utils/print_utils.py +102 -0
- flapjax/utils/utils.py +175 -0
- flapjax_full-1.2.0.dev0.dist-info/METADATA +66 -0
- flapjax_full-1.2.0.dev0.dist-info/RECORD +129 -0
- flapjax_full-1.2.0.dev0.dist-info/WHEEL +4 -0
- flapjax_full-1.2.0.dev0.dist-info/licenses/LICENSE +22 -0
flapjax/__init__.py
ADDED
|
File without changes
|
flapjax/aero/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from flapjax.aero.data_structures import (
|
|
2
|
+
AeroCase,
|
|
3
|
+
GridDiscretisation,
|
|
4
|
+
)
|
|
5
|
+
from flapjax.aero.flowfields import Constant, OneMinusCosine
|
|
6
|
+
from flapjax.aero.gradients.data_structures import (
|
|
7
|
+
AeroGradsToCompute,
|
|
8
|
+
AeroJacobianApproximations,
|
|
9
|
+
AeroStates,
|
|
10
|
+
)
|
|
11
|
+
from flapjax.aero.linear.data_structures import (
|
|
12
|
+
AeroInputUnflattened,
|
|
13
|
+
AeroLinearResult,
|
|
14
|
+
AeroOutputUnflattened,
|
|
15
|
+
AeroStateUnflattened,
|
|
16
|
+
)
|
|
17
|
+
from flapjax.aero.linear.linear_uvlm import LinearUVLM
|
|
18
|
+
from flapjax.aero.utils import add_control_surface, make_rectangular_grid
|
|
19
|
+
from flapjax.aero.uvlm import UVLM
|
flapjax/aero/aic.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
3
|
+
import jax
|
|
4
|
+
from jax import Array, vmap
|
|
5
|
+
from jax import numpy as jnp
|
|
6
|
+
|
|
7
|
+
from flapjax.aero.utils import KernelFunction, mirror_grid
|
|
8
|
+
from flapjax.algebra.array_utils import ArrayList, block_axis
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compute_aic_grid(
|
|
12
|
+
c: Array,
|
|
13
|
+
n: Array | None,
|
|
14
|
+
zeta: Array,
|
|
15
|
+
kernel: KernelFunction,
|
|
16
|
+
batch_size: int | None,
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Compute the aerodynamic influence coefficient (AIC) across grids of points. When normal is provided, fuses the dot
|
|
20
|
+
product inside each map step so the trailing 3-component axis is never accumulated, saving memory.
|
|
21
|
+
:param c: Collocation points, ``(c_m, c_n, 3)``.
|
|
22
|
+
:param n: Normal vectors at collocation points, ``(c_m, c_n, 3)``, or None.
|
|
23
|
+
:param zeta: Grid vertices, ``(zeta_m, zeta_n, 3)``.
|
|
24
|
+
:param kernel: Kernel function to compute the influence.
|
|
25
|
+
:param batch_size: Batch size for vectorising AIC computations.
|
|
26
|
+
:return: ``(c_m, c_n, zeta_m, zeta_n, 3)`` if normal is None, else ``(c_m, c_n, zeta_m, zeta_n)``.
|
|
27
|
+
"""
|
|
28
|
+
c_m, c_n = c.shape[:2]
|
|
29
|
+
m_panels, n_panels = zeta.shape[0] - 1, zeta.shape[1] - 1
|
|
30
|
+
|
|
31
|
+
m_vect_flat = jnp.stack((zeta[:-1, :, :], zeta[1:, :, :]), axis=-2).reshape(
|
|
32
|
+
-1, 2, 3
|
|
33
|
+
)
|
|
34
|
+
n_vect_flat = jnp.stack((zeta[:, :-1, :], zeta[:, 1:, :]), axis=-2).reshape(
|
|
35
|
+
-1, 2, 3
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# account for the degenerate case where there are no source panels to prevent division by zero
|
|
39
|
+
if not c_m or not c_n or not m_panels or not n_panels:
|
|
40
|
+
return jnp.zeros((c_m, c_n, m_panels, n_panels))
|
|
41
|
+
|
|
42
|
+
@jax.checkpoint
|
|
43
|
+
def row(args: tuple) -> Array:
|
|
44
|
+
# compute the influence of all spanwise (m) and chordwise (n) filaments before combining. This prevents any
|
|
45
|
+
# duplicate computations.
|
|
46
|
+
ci, ni = args
|
|
47
|
+
m_influence = vmap(kernel, (None, 0), 0)(ci, m_vect_flat)
|
|
48
|
+
m_influence_ni = jnp.dot(m_influence, ni).reshape(
|
|
49
|
+
m_panels, n_panels + 1
|
|
50
|
+
) # [m, n+1]
|
|
51
|
+
n_influence = vmap(kernel, (None, 0), 0)(ci, n_vect_flat)
|
|
52
|
+
n_influence_ni = jnp.dot(n_influence, ni).reshape(
|
|
53
|
+
m_panels + 1, n_panels
|
|
54
|
+
) # [m+1, n]
|
|
55
|
+
return -jnp.diff(m_influence_ni, axis=1) + jnp.diff(
|
|
56
|
+
n_influence_ni, axis=0
|
|
57
|
+
) # [m, n]
|
|
58
|
+
|
|
59
|
+
return jax.lax.map(
|
|
60
|
+
row,
|
|
61
|
+
(c.reshape(-1, 3), n.reshape(-1, 3) if n is not None else None),
|
|
62
|
+
batch_size=batch_size,
|
|
63
|
+
).reshape(c_m, c_n, m_panels, n_panels)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def compute_aic_sys(
|
|
67
|
+
zetas: ArrayList,
|
|
68
|
+
cs: ArrayList,
|
|
69
|
+
ns: ArrayList,
|
|
70
|
+
kernels: Sequence[KernelFunction],
|
|
71
|
+
batch_size: int | None,
|
|
72
|
+
mirror_point: Array | None,
|
|
73
|
+
mirror_normal: Array | None,
|
|
74
|
+
) -> list[list[Array]]:
|
|
75
|
+
"""
|
|
76
|
+
Compute the AIC matrix for a system of elements. Returns a list of AIC matrices, one for each element.
|
|
77
|
+
:param zetas: List of source points to compute the AIC from, ``(n_source,)(zeta_m, zeta_n, 3)``.
|
|
78
|
+
:param cs: List of target points to compute the AIC at, ``(n_target,)(c_m, c_n, 3)``.
|
|
79
|
+
:param ns: Bound normal vectors, ``(c_m, c_n, 3)``. If None, no projection will be done.
|
|
80
|
+
:param kernels: List of kernel functions to use for each source surface, ``(n_source, )``.
|
|
81
|
+
:param batch_size: Batch size for vectorising AIC computations.
|
|
82
|
+
:param mirror_normal: Normal vector to mirror across, ``(3, )``. If None, no mirroring will be done.
|
|
83
|
+
:param mirror_point: Point on mirror plane, ``(3, )``. If None, no mirroring will be done.
|
|
84
|
+
:return: Nested sequences of AIC matrices, ``(n_target,)(n_source, c_m, c_n, zeta_m, zeta_n, 3)``, or
|
|
85
|
+
``(n_target,)(n_source,)(c_m, c_n, zeta_m, zeta_n)`` if projected onto normals.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
aic_mats = []
|
|
89
|
+
for c, n in zip(cs, ns):
|
|
90
|
+
aic_mats.append([])
|
|
91
|
+
for zeta, kernel in zip(zetas, kernels):
|
|
92
|
+
# compute the AIC matrix, [n_cx, n_cy, n_ex, n_ey, 3]
|
|
93
|
+
aic_ = compute_aic_grid(
|
|
94
|
+
c=c,
|
|
95
|
+
n=n,
|
|
96
|
+
zeta=zeta,
|
|
97
|
+
kernel=kernel,
|
|
98
|
+
batch_size=batch_size,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if mirror_point is not None and mirror_normal is not None:
|
|
102
|
+
# add influence from mirrored grid, if specified
|
|
103
|
+
zeta_mirror = mirror_grid(
|
|
104
|
+
zeta=zeta,
|
|
105
|
+
mirror_point=mirror_point,
|
|
106
|
+
mirror_normal=mirror_normal,
|
|
107
|
+
)
|
|
108
|
+
aic_ -= compute_aic_grid(
|
|
109
|
+
c=c, n=n, zeta=zeta_mirror, kernel=kernel, batch_size=batch_size
|
|
110
|
+
)
|
|
111
|
+
aic_mats[-1].append(aic_)
|
|
112
|
+
return aic_mats
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def reshape_aic_sys(aic_mat: Array) -> Array:
|
|
116
|
+
r"""
|
|
117
|
+
Reshape an AIC matrix such that the source and target dimensions are flattened.
|
|
118
|
+
:param aic_mat: Input AIC matrix, ``(c_m, c_n, zeta_m, zeta_n)`` or ``(c_m, c_n, zeta_m, zeta_n, 3)``.
|
|
119
|
+
:return: Reshaped AIC matrix, ``(c_m*c_n, zeta_m*zeta_n)`` or ``(c_m*c_n, zeta_m*zeta_n, 3)``.
|
|
120
|
+
"""
|
|
121
|
+
shape = aic_mat.shape
|
|
122
|
+
return aic_mat.reshape([shape[0] * shape[1], shape[2] * shape[3]])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def assemble_aic_sys(aic_mats: Sequence[Sequence[Array]]) -> Array:
|
|
126
|
+
r"""
|
|
127
|
+
Assemble a nested sequence of AIC matrices into a single AIC matrix.
|
|
128
|
+
:param aic_mats: Nested sequence of AIC matrices, ``(n_target,)(n_source,)(c_m, c_n, zeta_m, zeta_n)`` or ``(n_target,)(n_source,)(c_m, c_n, zeta_m, zeta_n, 3)``.
|
|
129
|
+
:return: Assembled AIC matrix, ``(c_tot, zeta_tot)`` or ``(c_tot, zeta_tot, 3)``.
|
|
130
|
+
"""
|
|
131
|
+
aic_mats_reshaped = [
|
|
132
|
+
[reshape_aic_sys(aic) for aic in aic_row] for aic_row in aic_mats
|
|
133
|
+
]
|
|
134
|
+
return block_axis(aic_mats_reshaped, axes=(0, 1))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def compute_aic_solve(
|
|
138
|
+
cs: ArrayList,
|
|
139
|
+
ns: ArrayList,
|
|
140
|
+
zetas_b: ArrayList,
|
|
141
|
+
zetas_w: ArrayList | None,
|
|
142
|
+
kernels_b: Sequence[KernelFunction],
|
|
143
|
+
kernels_w: Sequence[KernelFunction] | None,
|
|
144
|
+
batch_size: int | None,
|
|
145
|
+
mirror_point: Array | None,
|
|
146
|
+
mirror_normal: Array | None,
|
|
147
|
+
) -> Array:
|
|
148
|
+
r"""
|
|
149
|
+
Compute the AIC matrix used for the UVLM solve step.
|
|
150
|
+
:param cs: List of target points to compute the AIC at, ``(n_target,)(c_m, c_n, 3)``.
|
|
151
|
+
:param ns: Bound normal vectors, ``(n_target,)(c_m, c_n, 3)``. If None, no projection will be done.
|
|
152
|
+
:param zetas_b: Bound aerodynamic grids, ``(n_source,)(zeta_m, zeta_n, 3)``.
|
|
153
|
+
:param zetas_w: Wake aerodynamic grids, ``(n_source,)(zeta_m_star, zeta_n, 3)``. This is only passed in the static case,
|
|
154
|
+
as in the dynamic case the wake influence is instead included in the boundary conditions.
|
|
155
|
+
:param kernels_b: Bound grid kernels.
|
|
156
|
+
:param kernels_w: Wake grid kernels.
|
|
157
|
+
:param batch_size: Batch size for vectorising AIC computations.
|
|
158
|
+
:param mirror_normal: Normal vector to mirror across, ``(3, )``. If None, no mirroring will be done.
|
|
159
|
+
:param mirror_point: Point on mirror plane, ``(3, )``. If None, no mirroring will be done.
|
|
160
|
+
:return: Square AIC matrix for the solve step, ``(c_tot, zeta_tot)``.
|
|
161
|
+
"""
|
|
162
|
+
aic_b_mats = compute_aic_sys(
|
|
163
|
+
cs=cs,
|
|
164
|
+
ns=ns,
|
|
165
|
+
zetas=zetas_b,
|
|
166
|
+
kernels=kernels_b,
|
|
167
|
+
batch_size=batch_size,
|
|
168
|
+
mirror_point=mirror_point,
|
|
169
|
+
mirror_normal=mirror_normal,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if zetas_w is not None:
|
|
173
|
+
if kernels_w is None:
|
|
174
|
+
raise ValueError("kernels_w must not be None")
|
|
175
|
+
aic_w_mats = compute_aic_sys(
|
|
176
|
+
cs=cs,
|
|
177
|
+
ns=ns,
|
|
178
|
+
zetas=zetas_w,
|
|
179
|
+
kernels=kernels_w,
|
|
180
|
+
batch_size=batch_size,
|
|
181
|
+
mirror_point=mirror_point,
|
|
182
|
+
mirror_normal=mirror_normal,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
aic_b_mats = add_wake_influence(aic_b_mats, aic_w_mats)
|
|
186
|
+
|
|
187
|
+
return assemble_aic_sys(aic_b_mats)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def add_wake_influence(
|
|
191
|
+
aic_bs: list[list[Array]], aic_ws: list[list[Array]]
|
|
192
|
+
) -> list[list[Array]]:
|
|
193
|
+
r"""
|
|
194
|
+
Lump the wake influence onto the last column of the bound AIC matrices. This captures the steady Kutta condition
|
|
195
|
+
by ensuring that the trailing edge panels have the same strength as all wake panels along a streamline.
|
|
196
|
+
:param aic_bs: Bound influence matrices, ``(n_target,)(n_source,)(c_m, c_n, zeta_m, zeta_n, 3)``.
|
|
197
|
+
:param aic_ws: Wake influence matrices, ``(n_target,)(n_source,)(c_m, c_n, zeta_m_star, zeta_n, 3)``.
|
|
198
|
+
:return: Updated bound influence matrices, ``(n_target,)(n_source,)(c_m, c_n, zeta_m, zeta_n, 3)``.
|
|
199
|
+
"""
|
|
200
|
+
for i in range(len(aic_bs)):
|
|
201
|
+
for j in range(len(aic_bs[i])):
|
|
202
|
+
aic_bs[i][j] = (
|
|
203
|
+
aic_bs[i][j].at[:, :, -1, :].add(jnp.sum(aic_ws[i][j], axis=2))
|
|
204
|
+
)
|
|
205
|
+
return aic_bs
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def v_ind_vmap(
|
|
209
|
+
c: Array,
|
|
210
|
+
zeta: Array,
|
|
211
|
+
gamma: Array,
|
|
212
|
+
kernel: KernelFunction,
|
|
213
|
+
batch_size: int | None,
|
|
214
|
+
) -> Array:
|
|
215
|
+
"""
|
|
216
|
+
Compute the induced velocity by the aerodynamic elements at some points in space for a single source-target panel
|
|
217
|
+
system. This is done without materialising the full AIC matrix, instead directly computing its contraction with the
|
|
218
|
+
circulation strength.
|
|
219
|
+
:param c: Points at which to sample the velocity, ``(c_m, c_n, 3)``.
|
|
220
|
+
:param zeta: Filament grid, ``(zeta_m, zeta_n, 2, 3)``.
|
|
221
|
+
:param gamma: Circulation strengths, ``(zeta_m, zeta_n)``.
|
|
222
|
+
:param kernel: Kernel function.
|
|
223
|
+
:param batch_size: Batch size for vectorising AIC computations.
|
|
224
|
+
:return: Induced velocity, ``(c_m, c_n, 3)``.
|
|
225
|
+
"""
|
|
226
|
+
c_m, c_n = c.shape[:2]
|
|
227
|
+
c_flat = c.reshape(-1, 3)
|
|
228
|
+
zeta_flat = zeta.reshape(-1, 2, 3)
|
|
229
|
+
gamma_flat = gamma.ravel() # [zeta_m * zeta_n]
|
|
230
|
+
|
|
231
|
+
# account for case where zeta is empty
|
|
232
|
+
if zeta.size == 0:
|
|
233
|
+
return jnp.zeros_like(c)
|
|
234
|
+
|
|
235
|
+
@jax.checkpoint
|
|
236
|
+
def row(ci: Array) -> Array:
|
|
237
|
+
influence = vmap(kernel, (None, 0), 0)(ci, zeta_flat) # [zeta_m * zeta_n, 3]
|
|
238
|
+
|
|
239
|
+
if influence.shape[0] != gamma_flat.shape[0]:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
return jnp.einsum("lm,l->m", influence, gamma_flat) # [3]
|
|
243
|
+
|
|
244
|
+
result = jax.lax.map(row, c_flat, batch_size=batch_size) # [c_m * c_n, 3]
|
|
245
|
+
return result.reshape(c_m, c_n, 3)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def compute_v_ind[T: Array | ArrayList](
|
|
249
|
+
cs: T,
|
|
250
|
+
zetas: ArrayList,
|
|
251
|
+
gammas: ArrayList,
|
|
252
|
+
kernels: Sequence[KernelFunction],
|
|
253
|
+
mirror_point: Array | None,
|
|
254
|
+
mirror_normal: Array | None,
|
|
255
|
+
batch_size: int | None,
|
|
256
|
+
) -> T:
|
|
257
|
+
"""
|
|
258
|
+
Compute the induced velocity by multiple surfaces of aerodynamic elements at one or multiple grids of points in
|
|
259
|
+
space. This is done without materialising the full AIC matrix, instead directly computing its contraction with the circulation strength.
|
|
260
|
+
:param cs: Points at which to sample the velocity, ``(c_m, c_n, 3)`` or ``(n_target,)(c_m, c_n, 3)``.
|
|
261
|
+
:param zetas: Filament grid, ``(n_source,)(zeta_m, zeta_n, 3)``.
|
|
262
|
+
:param gammas: Circulation strengths, ``(n_source,)(zeta_m, zeta_n)``.
|
|
263
|
+
:param kernels: Kernel function.
|
|
264
|
+
:param mirror_point: Mirror point, ``(3, )``. If None, no mirroring will be done.
|
|
265
|
+
:param mirror_normal: Normal mirror vector, ``(3, )``. If None, no mirroring will be done.
|
|
266
|
+
:param batch_size: Batch size for vectorising AIC computations.
|
|
267
|
+
:return: Array or ArrayList of induced velocity, ``(c_m, c_n, 3)`` or ``(n_target,)(c_m, c_n, 3)``.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
# convert cs to an ArrayList. If it is an Array, we will convert back before returning.
|
|
271
|
+
cs_: ArrayList = ArrayList([cs]) if isinstance(cs, Array) else cs
|
|
272
|
+
|
|
273
|
+
v = ArrayList([])
|
|
274
|
+
for c in cs_:
|
|
275
|
+
v.append(jnp.zeros_like(c))
|
|
276
|
+
for zeta, gamma, kernel in zip(zetas, gammas, kernels):
|
|
277
|
+
m_vect = jnp.stack(
|
|
278
|
+
(zeta[:-1, :, :], zeta[1:, :, :]), axis=-2
|
|
279
|
+
) # [m, n+1, 2, 3]
|
|
280
|
+
n_vect = jnp.stack(
|
|
281
|
+
(zeta[:, :-1, :], zeta[:, 1:, :]), axis=-2
|
|
282
|
+
) # [m+1, n, 2, 3]
|
|
283
|
+
|
|
284
|
+
gamma_eff_m = jnp.diff(jnp.pad(gamma, ((0, 0), (1, 1))), axis=1) # [m, n+1]
|
|
285
|
+
gamma_eff_n = -jnp.diff(
|
|
286
|
+
jnp.pad(gamma, ((1, 1), (0, 0))), axis=0
|
|
287
|
+
) # [m+1, n]
|
|
288
|
+
|
|
289
|
+
v[-1] += v_ind_vmap(
|
|
290
|
+
c, m_vect, gamma_eff_m, kernel, batch_size
|
|
291
|
+
) + v_ind_vmap(c, n_vect, gamma_eff_n, kernel, batch_size)
|
|
292
|
+
|
|
293
|
+
if mirror_point is not None and mirror_normal is not None:
|
|
294
|
+
zeta_mirror = mirror_grid(
|
|
295
|
+
zeta=zeta,
|
|
296
|
+
mirror_point=mirror_point,
|
|
297
|
+
mirror_normal=mirror_normal,
|
|
298
|
+
)
|
|
299
|
+
m_vect_mirror = jnp.stack(
|
|
300
|
+
(zeta_mirror[:-1, :, :], zeta_mirror[1:, :, :]), axis=-2
|
|
301
|
+
) # [m, n+1, 2, 3]
|
|
302
|
+
n_vect_mirror = jnp.stack(
|
|
303
|
+
(zeta_mirror[:, :-1, :], zeta_mirror[:, 1:, :]), axis=-2
|
|
304
|
+
) # [m+1, n, 2, 3]
|
|
305
|
+
|
|
306
|
+
v[-1] -= v_ind_vmap(
|
|
307
|
+
c, m_vect_mirror, gamma_eff_m, kernel, batch_size
|
|
308
|
+
) + v_ind_vmap(c, n_vect_mirror, gamma_eff_n, kernel, batch_size)
|
|
309
|
+
|
|
310
|
+
return v[0] if isinstance(cs, Array) else v
|