"""Generic diagnostic view for solved line-element families."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
import numpy as np
from buffalo_core import as_float_array
from buffalo_panel.families.element_family import ElementFamily
from buffalo_panel.geometry.line2d import LineKernelGeometry2D
from buffalo_panel.kernels.interfaces import (
ElementKernel,
KernelEvaluationOptions,
)
from buffalo_panel.post import FieldPoints2D
from buffalo_panel.type_aliases import FloatArray, FloatInput
[docs]
class LineFieldView2D[GeometryT_co: LineKernelGeometry2D](Protocol):
"""Read-only protocol for objects that evaluate one line-family field."""
@property
def geometry(self) -> GeometryT_co:
"""Return the geometry used to evaluate the field."""
...
[docs]
def velocity_at(
self,
x: FloatInput,
y: FloatInput,
) -> tuple[FloatArray, FloatArray]:
"""Evaluate induced velocity at field points."""
...
[docs]
def potential_at(self, x: FloatInput, y: FloatInput) -> FloatArray:
"""Evaluate induced potential at field points."""
...
[docs]
def stream_function_at(
self,
x: FloatInput,
y: FloatInput,
) -> FloatArray:
"""Evaluate induced stream function at field points."""
...
[docs]
@dataclass(frozen=True, slots=True)
class LineFamily2DView[GeometryT: LineKernelGeometry2D]:
"""
Diagnostic field view for one line-element family.
The view evaluates velocity, potential, and stream-function contributions
by combining family-local unit-influence blocks with an explicit vector of
family-local coefficients.
This makes the view layer compatible with constant, shared, and future
higher-order families without assuming one scalar strength per panel.
"""
geometry: GeometryT
"""Geometry containing the family source panels."""
family: ElementFamily
"""Element family defining panel ownership and local coefficient layout."""
local_coefficients: FloatInput
"""Solved or user-specified family-local coefficients."""
kernel: ElementKernel[GeometryT]
"""Kernel used to evaluate family-local unit influence blocks."""
top: bool = True
"""Branch-side convention for points on panel cuts."""
def __post_init__(self) -> None:
"""Normalize and validate stored family-local coefficients."""
coefficients = as_float_array(self.local_coefficients).reshape(-1)
if coefficients.size != self.family.n_local_dofs:
raise ValueError(
"local_coefficients size must match family.n_local_dofs."
)
object.__setattr__(self, "local_coefficients", coefficients)
[docs]
def velocity_at(
self,
x: FloatInput,
y: FloatInput,
) -> tuple[FloatArray, FloatArray]:
"""
Evaluate induced velocity at field points.
Parameters
----------
x : FloatInput
Field-point x-coordinates.
y : FloatInput
Field-point y-coordinates.
Returns
-------
FloatArray
Global x-velocity contribution with the broadcast input shape.
FloatArray
Global y-velocity contribution with the broadcast input shape.
"""
points = FieldPoints2D.from_inputs(x, y)
options = KernelEvaluationOptions(top=self.top)
u_block, v_block = self.kernel.build_field_velocity_blocks(
self.family,
self.geometry,
points.x_flat,
points.y_flat,
options,
)
coefficients = np.asarray(self.local_coefficients, dtype=np.float64)
return (
points.reshape_values(u_block @ coefficients),
points.reshape_values(v_block @ coefficients),
)
[docs]
def potential_at(self, x: FloatInput, y: FloatInput) -> FloatArray:
"""
Evaluate induced velocity potential at field points.
Parameters
----------
x : FloatInput
Field-point x-coordinates.
y : FloatInput
Field-point y-coordinates.
Returns
-------
FloatArray
Velocity potential contribution with the broadcast input shape.
"""
points = FieldPoints2D.from_inputs(x, y)
options = KernelEvaluationOptions(top=self.top)
block = self.kernel.build_field_potential_block(
self.family,
self.geometry,
points.x_flat,
points.y_flat,
options,
)
coefficients = np.asarray(self.local_coefficients, dtype=np.float64)
return points.reshape_values(block @ coefficients)
[docs]
def stream_function_at(self, x: FloatInput, y: FloatInput) -> FloatArray:
"""
Evaluate induced stream function at field points.
Parameters
----------
x : FloatInput
Field-point x-coordinates.
y : FloatInput
Field-point y-coordinates.
Returns
-------
FloatArray
Stream-function contribution with the broadcast input shape.
"""
points = FieldPoints2D.from_inputs(x, y)
options = KernelEvaluationOptions(top=self.top)
block = self.kernel.build_field_stream_function_block(
self.family,
self.geometry,
points.x_flat,
points.y_flat,
options,
)
coefficients = np.asarray(self.local_coefficients, dtype=np.float64)
return points.reshape_values(block @ coefficients)
[docs]
def field_at(
self,
x: FloatInput,
y: FloatInput,
) -> tuple[FloatArray, FloatArray, FloatArray, FloatArray]:
"""
Evaluate velocity, potential, and stream function together.
Parameters
----------
x : FloatInput
Field-point x-coordinates.
y : FloatInput
Field-point y-coordinates.
Returns
-------
FloatArray
Global x-velocity contribution.
FloatArray
Global y-velocity contribution.
FloatArray
Velocity potential contribution.
FloatArray
Stream-function contribution.
"""
u, v = self.velocity_at(x, y)
potential = self.potential_at(x, y)
stream_function = self.stream_function_at(x, y)
return u, v, potential, stream_function