Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions package/src/openflash/basic_region_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,17 @@ def from_vectors(cls,
validation to prevent invalid body_map/heaving_map combinations and
enforces the global monotonicity invariant required by BasicRegionGeometry.
"""
a = np.atleast_1d(np.asarray(a, dtype=float))
d = np.atleast_1d(np.asarray(d, dtype=float))
if len(a) != len(d):
raise ValueError("Input vectors 'a' and 'd' must have the same length.")

if slant_angle is None:
slant_angle = np.zeros_like(a)
else:
slant_angle = np.atleast_1d(np.asarray(slant_angle, dtype=float))
if len(slant_angle) != len(a):
raise ValueError("Input vector 'slant_angle' must have the same length as 'a'.")

if body_map is None:
body_map = [0] * len(a)
Expand Down
4 changes: 4 additions & 0 deletions package/src/openflash/body.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class SteppedBody(Body):
heaving (bool, optional): Flag indicating if the entire body is heaving. Defaults to False.
"""
def __init__(self, a: np.ndarray, d: np.ndarray, slant_angle: np.ndarray, heaving: bool = False):
a = np.atleast_1d(np.asarray(a, dtype=float))
d = np.atleast_1d(np.asarray(d, dtype=float))
slant_angle = np.atleast_1d(np.asarray(slant_angle, dtype=float))

assert len(a) == len(d) == len(slant_angle), "Input arrays a, d, and slant_angle must have the same length."
self.a = a
self.d = d
Expand Down
3 changes: 1 addition & 2 deletions package/src/openflash/meem_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ def set_frequencies(self, frequencies: np.ndarray):
frequencies : np.ndarray
Array of angular frequencies or omega (rad/s).
"""
frequencies = np.atleast_1d(np.asarray(frequencies, dtype=float))
assert np.all(frequencies > 0), "All frequencies must be positive"
assert isinstance(frequencies, np.ndarray), "frequencies must be a numpy array"

self.frequencies = frequencies

@property
Expand Down
195 changes: 195 additions & 0 deletions package/test/test_input_type_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import os
import sys

import numpy as np
import pytest

current_dir = os.path.dirname(__file__)
src_dir = os.path.abspath(os.path.join(current_dir, "..", "src"))
if src_dir not in sys.path:
sys.path.insert(0, src_dir)

from openflash.body import SteppedBody
from openflash.geometry import ConcentricBodyGroup
from openflash.basic_region_geometry import BasicRegionGeometry
from openflash.meem_problem import MEEMProblem
from openflash.meem_engine import MEEMEngine
from openflash import multi_equations as me


def _assert_close(left, right):
np.testing.assert_allclose(np.asarray(left), np.asarray(right), rtol=1e-12, atol=1e-12, equal_nan=True)


@pytest.mark.parametrize(
"a,d,slant",
[
(1.5, 0.5, 0.0),
(np.array(1.5), np.array(0.5), np.array(0.0)),
(1.5, np.array(0.5), 0.0),
],
)
def test_stepped_body_scalar_input_variants(a, d, slant):
body = SteppedBody(a=a, d=d, slant_angle=slant, heaving=True)
np.testing.assert_array_equal(body.a, np.array([1.5]))
np.testing.assert_array_equal(body.d, np.array([0.5]))
np.testing.assert_array_equal(body.slant_angle, np.array([0.0]))
assert body.heaving


@pytest.mark.parametrize(
"a,d,slant",
[
([1.0, 2.0], [0.5, 0.75], [0.0, 0.1]),
(np.array([1.0, 2.0]), [0.5, 0.75], np.array([0.0, 0.1])),
([1.0, 2.0], np.array([0.5, 0.75]), np.array([0.0, 0.1])),
],
)
def test_stepped_body_vector_input_variants(a, d, slant):
body = SteppedBody(a=a, d=d, slant_angle=slant, heaving=False)
np.testing.assert_array_equal(body.a, np.array([1.0, 2.0]))
np.testing.assert_array_equal(body.d, np.array([0.5, 0.75]))
np.testing.assert_array_equal(body.slant_angle, np.array([0.0, 0.1]))


def test_concentric_body_group_mixed_step_input_types():
body_0 = SteppedBody(a=1.0, d=np.array(0.5), slant_angle=0.0, heaving=True)
body_1 = SteppedBody(a=[2.0, 3.0], d=np.array([1.0, 1.5]), slant_angle=[0.0, 0.2], heaving=False)
group = ConcentricBodyGroup([body_0, body_1])

np.testing.assert_array_equal(group.a, np.array([1.0, 2.0, 3.0]))
np.testing.assert_array_equal(group.d, np.array([0.5, 1.0, 1.5]))
np.testing.assert_array_equal(group.slant_angle, np.array([0.0, 0.0, 0.2]))
np.testing.assert_array_equal(group.heaving, np.array([True, False, False]))


@pytest.mark.parametrize(
"a,d,slant,body_map,heaving_map",
[
([1.0, 2.0], [0.5, 0.8], [0.0, 0.1], [0, 1], [True, False]),
(np.array([1.0, 2.0]), np.array([0.5, 0.8]), np.array([0.0, 0.1]), [0, 1], np.array([True, False])),
([1.0, 2.0], np.array([0.5, 0.8]), None, None, None),
],
)
def test_basic_region_geometry_from_vectors_type_variants(a, d, slant, body_map, heaving_map):
geom = BasicRegionGeometry.from_vectors(
a=a,
d=d,
h=np.array(10.0),
NMK=[3, 3, 3],
slant_angle=slant,
body_map=body_map,
heaving_map=heaving_map,
)
np.testing.assert_array_equal(geom.body_arrangement.a, np.array([1.0, 2.0]))
assert len(geom.domain_list) == 3


@pytest.mark.parametrize(
"frequencies,expected",
[
(0.5, np.array([0.5])),
(np.array(0.5), np.array([0.5])),
([0.5, 1.0], np.array([0.5, 1.0])),
(np.array([0.5, 1.0]), np.array([0.5, 1.0])),
],
)
def test_meem_problem_set_frequencies_type_variants(frequencies, expected):
geom = BasicRegionGeometry.from_vectors(a=[1.0], d=[0.5], h=10.0, NMK=[3, 3], heaving_map=[True])
problem = MEEMProblem(geometry=geom)
problem.set_frequencies(frequencies)
np.testing.assert_array_equal(problem.frequencies, expected)


def test_meem_engine_with_mixed_instantiation_types():
geom = BasicRegionGeometry.from_vectors(
a=[1.0, 2.0],
d=np.array([0.5, 0.8]),
h=np.array(10.0),
NMK=[3, 3, 3],
body_map=[0, 1],
heaving_map=[True, False],
)
problem = MEEMProblem(geometry=geom)
problem.set_frequencies(np.array(0.7))

engine = MEEMEngine(problem_list=[problem])
A = engine.assemble_A_multi(problem, m0=0.4)
b = engine.assemble_b_multi(problem, m0=0.4)

assert isinstance(A, np.ndarray)
assert isinstance(b, np.ndarray)
assert A.shape[0] == A.shape[1]
assert b.shape[0] == A.shape[0]


def test_multi_equations_scalar_input_equivalence_float_vs_scalar_array():
_assert_close(me.omega(0.1, 100.0, 9.81), me.omega(np.array(0.1), np.array(100.0), np.array(9.81)))
_assert_close(me.wavenumber(1.2, 100.0), me.wavenumber(np.array(1.2), np.array(100.0)))
_assert_close(me.m_k_entry(1, 0.1, 100.0), me.m_k_entry(1, np.array(0.1), np.array(100.0)))


def test_multi_equations_vector_input_equivalence_list_vs_ndarray_and_mixed():
h = 100.0
m0 = 0.1
a_list, a_arr = [5.0, 10.0, 15.0], np.array([5.0, 10.0, 15.0])
d_list, d_arr = [0.0, 20.0, 50.0], np.array([0.0, 20.0, 50.0])
heaving_list, heaving_arr = [1.0, 0.5, 0.0], np.array([1.0, 0.5, 0.0])
m_k_arr = np.array([0.1, 0.2, 0.3, 0.4])
N_k_arr = np.array([0.9, 1.0, 1.1, 1.2])

method_calls = [
(
lambda d, hv, a: me.I_nm(1, 1, 0, d, h),
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_arr, heaving_list, a_arr),
),
(
lambda d, hv, a: me.I_mk(1, 1, 1, d, m0, h, m_k_arr, N_k_arr),
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_list, heaving_arr, a_arr),
),
(
lambda d, hv, a: me.b_potential_entry(1, 1, d, hv, h, a),
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_arr, heaving_list, a_arr),
),
(
lambda d, hv, a: me.b_velocity_entry(1, 0, hv, a, h, d),
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_list, heaving_arr, a_arr),
),
(
lambda d, hv, a: me.R_1n_vectorized(np.array([0, 1]), np.array([7.0, 7.0]), 1, h, d, a),
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_arr, heaving_list, a_arr),
),
(
lambda d, hv, a: me.int_R_1n(1, 1, a, h, d),
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_arr, heaving_arr, a_list),
),
(
lambda d, hv, a: me.make_R_Z(a, h, d, sharp=True, spatial_res=8)[0],
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_list, heaving_arr, a_arr),
),
(
lambda d, hv, a: me.make_R_Z(a, h, d, sharp=True, spatial_res=8)[1],
(d_list, heaving_list, a_list),
(d_arr, heaving_arr, a_arr),
(d_list, heaving_arr, a_arr),
),
]

for method, args_list, args_array, args_mixed in method_calls:
expected = method(*args_array)
_assert_close(method(*args_list), expected)
_assert_close(method(*args_mixed), expected)
Loading