Source code for openflash.geometry
# geometry.py
from abc import ABC, abstractmethod
from typing import List
import numpy as np
from .body import Body, SteppedBody, CoordinateBody
from .domain import Domain
[docs]
class BodyArrangement(ABC):
"""
Abstract base class for any arrangement.
"""
def __init__(self, bodies: List[Body]):
self.bodies = bodies
@property
@abstractmethod
def a(self) -> np.ndarray:
"""Array of characteristic radii."""
pass
@property
@abstractmethod
def d(self) -> np.ndarray:
"""Array of characteristic d."""
pass
@property
@abstractmethod
def slant_angle(self) -> np.ndarray:
"""Array of slant angles."""
pass
@property
@abstractmethod
def heaving(self) -> np.ndarray:
"""Array of heaving flags."""
pass
[docs]
class ConcentricBodyGroup(BodyArrangement):
"""
A concrete arrangement of one or more concentric bodies.
For JOSS, this class assumes all bodies are SteppedBody objects.
"""
def __init__(self, bodies: List[Body]):
super().__init__(bodies)
# For now, we only handle SteppedBody
for body in self.bodies:
if not isinstance(body, SteppedBody):
raise TypeError("ConcentricBodyGroup currently only supports SteppedBody objects.")
def _get_concatenated_property(self, prop_name: str) -> np.ndarray:
"""Helper to concatenate a property from all SteppedBody objects."""
return np.concatenate([getattr(body, prop_name) for body in self.bodies])
def _get_heaving_flags(self) -> np.ndarray:
"""Helper to create a heaving flag array based on each body."""
flags = []
for body in self.bodies:
num_steps = len(body.a)
flags.extend([body.heaving] * num_steps)
return np.array(flags, dtype=bool)
@property
def a(self) -> np.ndarray:
return self._get_concatenated_property('a')
@property
def d(self) -> np.ndarray:
return self._get_concatenated_property('d')
@property
def slant_angle(self) -> np.ndarray:
return self._get_concatenated_property('slant_angle')
@property
def heaving(self) -> np.ndarray:
return self._get_heaving_flags()
[docs]
class Geometry(ABC):
"""
Abstract base class for a complete problem geometry.
A Geometry consists of a BodyArrangement and the total water depth, and
it is responsible for creating the corresponding fluid domains.
"""
def __init__(self, body_arrangement: BodyArrangement, h: float):
self.body_arrangement = body_arrangement
self.h = h
self._fluid_domains: List[Domain] = []
@property
def fluid_domains(self) -> List[Domain]:
if not self._fluid_domains:
self._fluid_domains = self.make_fluid_domains()
return self._fluid_domains
[docs]
@abstractmethod
def make_fluid_domains(self) -> List[Domain]:
"""Creates the list of Domain objects from the BodyArrangement."""
pass