Source code for buffalo_panel.formulations.hess_smith
"""Public Hess-Smith formulation entry points."""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from buffalo_core import as_int_array
from buffalo_panel.assembly import Freestream2D
from buffalo_panel.assembly.internal.system import (
build_hess_smith_open_trailing_edge_system,
build_hess_smith_system,
)
from buffalo_panel.basis.descriptors import LINE_CONSTANT
from buffalo_panel.families.dof_maps import PerElementDofMap, SharedGlobalDofMap
from buffalo_panel.families.element_family import ElementFamily
from buffalo_panel.geometry.line2d import LinePanelGeometry2D
from buffalo_panel.geometry.supports import LINE_2D
from buffalo_panel.kernels.registry import KernelRegistry
from buffalo_panel.post import BodyReference2D, PanelSolution2D
from buffalo_panel.post.internal.hess_smith import recover_hess_smith_solution
from buffalo_panel.prescribed.internal.point_element_particle_2d import (
PointElementParticle2D,
)
from buffalo_panel.singularities.descriptors import SOURCE, VORTEX
from buffalo_panel.solvers.dense import solve_dense
from buffalo_panel.type_aliases import FloatArray
@dataclass(slots=True)
class OpenTrailingEdgeFamilies:
"""Required trailing-edge families for open-geometry Hess-Smith cases."""
source: ElementFamily
"""Trailing-edge source family."""
vortex: ElementFamily
"""Trailing-edge vortex family."""
[docs]
@dataclass(slots=True)
class HessSmithFormulation:
"""Solve the classic Hess-Smith source-plus-vortex formulation."""
geometry: LinePanelGeometry2D
"""Geometry used in the Hess-Smith solver."""
registry: KernelRegistry
"""Registry of computational kernels to use in Hess-Smith solver."""
backend: str = "python"
"""Specific computational kernel backend to use."""
body_reference: BodyReference2D | None = None
"""Reference quantities for integrated coefficient recovery."""
[docs]
def build_solution_families(
self,
) -> tuple[tuple[ElementFamily, ...], tuple[ElementFamily, ...]]:
"""
Build the source and vortex families used by this formulation.
Returns
-------
tuple[tuple[ElementFamily, ...], tuple[ElementFamily, ...]]
Source-family tuple followed by vortex-family tuple in the order
required by the current Hess-Smith assembly and recovery paths.
"""
source_family = self.build_body_source_family()
vortex_family = self.build_body_vortex_family()
source_families: list[ElementFamily] = [source_family]
vortex_families: list[ElementFamily] = [vortex_family]
if not self.geometry.is_closed:
te_families = self.build_open_trailing_edge_families()
source_families.append(te_families.source)
vortex_families.append(te_families.vortex)
return tuple(source_families), tuple(vortex_families)
[docs]
def build_body_source_family(self) -> ElementFamily:
"""
Build the constant source family for the body panels.
Returns
-------
ElementFamily
Body source panel assembly for this formulation.
"""
n_body_panels = self.geometry.n_body_panels
body_source_index = np.arange(n_body_panels, dtype=np.int32)
return ElementFamily(
name="body_sources",
support=LINE_2D,
singularity=SOURCE,
basis=LINE_CONSTANT,
n_elements=n_body_panels,
panel_indices=self.geometry.body_panel_indices,
dof_map=PerElementDofMap(global_indices=body_source_index),
metadata={"role": "body"},
)
[docs]
def build_body_vortex_family(self) -> ElementFamily:
"""
Build the shared constant vortex family for the body panels.
Returns
-------
ElementFamily
Body vortex panel assembly for this formulation.
"""
n_body_panels = self.geometry.n_body_panels
body_vortex_index = n_body_panels
return ElementFamily(
name="body_vortex_shared",
support=LINE_2D,
singularity=VORTEX,
basis=LINE_CONSTANT,
n_elements=n_body_panels,
panel_indices=self.geometry.body_panel_indices,
dof_map=SharedGlobalDofMap(
global_index=body_vortex_index,
n_elements=n_body_panels,
),
metadata={"role": "body", "shared_gamma": True},
)
[docs]
def build_trailing_edge_source_family(self) -> ElementFamily | None:
"""
Build the constant source family for the TE closure panel.
Returns
-------
ElementFamily | None
Trailing edge source panel assembly for this formulation, if
geometry is open, otherwise `None`.
"""
te_panel_index = self.geometry.trailing_edge_panel_index
if te_panel_index is None:
return None
n_body_panels = self.geometry.n_body_panels
te_source_index = as_int_array([n_body_panels + 1])
return ElementFamily(
name="trailing_edge_source",
support=LINE_2D,
singularity=SOURCE,
basis=LINE_CONSTANT,
n_elements=1,
panel_indices=as_int_array([te_panel_index]),
dof_map=PerElementDofMap(global_indices=te_source_index),
metadata={"role": "trailing_edge"},
)
[docs]
def build_trailing_edge_vortex_family(self) -> ElementFamily | None:
"""
Build the constant vortex family for the TE closure panel.
Returns
-------
ElementFamily | None
Trailing edge vortex panel assembly for this formulation, if
geometry is open, otherwise `None`.
"""
te_panel_index = self.geometry.trailing_edge_panel_index
if te_panel_index is None:
return None
n_body_panels = self.geometry.n_body_panels
te_vortex_index = as_int_array([n_body_panels + 2])
return ElementFamily(
name="trailing_edge_vortex",
support=LINE_2D,
singularity=VORTEX,
basis=LINE_CONSTANT,
n_elements=1,
panel_indices=as_int_array([te_panel_index]),
dof_map=PerElementDofMap(global_indices=te_vortex_index),
metadata={"role": "trailing_edge", "shared_gamma": False},
)
[docs]
def build_open_trailing_edge_families(self) -> OpenTrailingEdgeFamilies:
"""
Build the required trailing-edge families for an open geometry.
Returns
-------
OpenTrailingEdgeFamilies
Trailing-edge source and vortex families for the closure panel.
Raises
------
ValueError
If called for a closed geometry that has no trailing-edge panel.
"""
te_source_family = self.build_trailing_edge_source_family()
te_vortex_family = self.build_trailing_edge_vortex_family()
if te_source_family is None or te_vortex_family is None:
raise ValueError(
"Open trailing-edge families require a geometry with a "
"trailing-edge closure panel."
)
return OpenTrailingEdgeFamilies(
source=te_source_family,
vortex=te_vortex_family,
)
[docs]
def solve(
self,
freestream: Freestream2D,
point_particles: tuple[PointElementParticle2D, ...] = (),
) -> tuple[FloatArray, PanelSolution2D]:
"""
Assemble, solve, and recover surface results for one freestream.
Parameters
----------
freestream : Freestream2D
Freestream conditions for this solution.
point_particles : tuple[PointElementParticle2D, ...], optional
Prescribed point particles whose induced fields contribute known
forcing to the body boundary condition and to recovered flow
fields.
Returns
-------
FloatArray
Raw solution from the system of equations.
PanelSolution2D
Processed results information from solution.
"""
source_families, vortex_families = self.build_solution_families()
source_family = source_families[0]
vortex_family = vortex_families[0]
if self.geometry.is_closed:
matrix, rhs = build_hess_smith_system(
source_family,
vortex_family,
self.geometry,
freestream,
self.registry,
self.backend,
point_particles,
)
else:
te_source_family = source_families[1]
te_vortex_family = vortex_families[1]
matrix, rhs = build_hess_smith_open_trailing_edge_system(
source_family,
vortex_family,
te_source_family,
te_vortex_family,
self.geometry,
freestream,
self.registry,
self.backend,
point_particles,
)
x = solve_dense(matrix, rhs)
results = recover_hess_smith_solution(
x,
source_families,
vortex_families,
self.geometry,
freestream,
self.registry,
self.backend,
self.body_reference,
point_particles,
)
return x, results