Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pyvale-output/
pyvale-input/
test-output/
test-input/
tests/strain/test/

# MOOSE / gmsh
*-opt
Expand Down
77 changes: 56 additions & 21 deletions src/pyvale/verif/pointsens.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,61 @@
"""

import numpy as np
import platform
import pyvale.mooseherder as mh
import pyvale.sensorsim as sens
import pyvale.dataio as io
import pyvale.verif.pointsensconst as pointsensconst
from pyvale.verif.pointsensconst import GOLD_SEED

def joggle_meshfree_coords(
coords: np.ndarray,
scale_factor: float = 1e-10,
seed: int = GOLD_SEED,
) -> np.ndarray:
"""
Apply a deterministic perturbation to coordinate point clouds to
break geometric degeneracies (e.g. co-circular/co-spherical points) in
Delaunay triangulation across platforms and architectures.

Preserves bounding box planes so that sensors placed on outer faces remain
inside the convex hull without producing out-of-bounds NaNs.

Parameters
----------
coords : np.ndarray
Array of nodal coordinates with shape (n_nodes, 3).
scale_factor : float, optional
Scale factor for coordinate perturbation relative to mesh bounding box
extent, by default 1e-10.
seed : int, optional
RNG seed for deterministic jitter, by default GOLD_SEED.

Returns
-------
np.ndarray
Perturbed coordinates array with shape (n_nodes, 3).
"""
rng = np.random.default_rng(seed)
char_length = float(np.ptp(coords, axis=0).max())
jitter = scale_factor * char_length * rng.standard_normal(coords.shape)

coord_min = coords.min(axis=0)
coord_max = coords.max(axis=0)
tol = 1e-7

# Preserve bounding box faces to keep boundary sensors inside convex hull
for dd in range(coords.shape[1]):
mask_min = np.isclose(coords[:, dd], coord_min[dd], atol=tol)
mask_max = np.isclose(coords[:, dd], coord_max[dd], atol=tol)
jitter[mask_min, dd] = 0.0
jitter[mask_max, dd] = 0.0

if np.allclose(coords[:, 2], 0.0):
jitter[:, 2] = 0.0

return coords + jitter


def samp_times(sim_data: io.SimData) -> dict[str, None | np.ndarray]:
sim_dims = sens.simtools.get_sim_dims(sim_data)
sample_times = {}
Expand Down Expand Up @@ -95,7 +143,7 @@ def gen_gold_measurements(sens_dict: dict[str,sens.SensorsPoint]) -> None:
for ss in sens_dict:
print(f"Generating gold output for case: {ss}")
measurements = sens_dict[ss].sim_measurements()
save_path = pointsensconst.GOLD_PATH / f"{ss}.npy"
save_path = pointsensconst.GOLD_PATH / f"{ss.lower()}.npy"
np.save(save_path,measurements)


Expand All @@ -104,32 +152,19 @@ def check_gold_measurements(sens_dict: dict[str,sens.SensorsPoint],
atol: float = 1e-5) -> list[str]:
fails = []

MACOS_GOLD_CASES = {
"scal2d_analytic_nomesh",
"scal3d_nomesh",
"vec2d_analytic_nomesh",
"tens2d_analytic_nomesh",
}

for ss in sens_dict:
measurements = sens_dict[ss].sim_measurements()
gold_path = pointsensconst.GOLD_PATH / f"{ss}.npy"

print(ss.lower())

if (any(case in ss.lower() for case in MACOS_GOLD_CASES) and (platform.system()=='Darwin')):
load_path = (pointsensconst.GOLD_PATH / "macos" / f"{ss.lower()}.npy")
else:
load_path = (pointsensconst.GOLD_PATH / f"{ss.lower()}.npy")
gold_path = pointsensconst.GOLD_PATH / f"{ss.lower()}.npy"


if load_path.is_file():
gold = np.load(load_path)
if gold_path.is_file():
gold = np.load(gold_path)

if not np.allclose(measurements,gold,rtol=rtol,atol=atol):
fails.append(f"Gold check failed for: {ss}")
else:
fails.append(f"Gold file does not exist for: {ss}, path: {gold_path}")
fails.append(
f"Gold file does not exist for: {ss}, path: {gold_path}"
)

return fails

Expand Down
5 changes: 4 additions & 1 deletion src/pyvale/verif/pointsensmech.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def simdata_mech_2d() -> io.SimData:
def simdata_mesh_2d_nomesh() -> io.SimData:
sim_data = simdata_mech_2d()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
return sim_data

def simdata_mech_3d() -> io.SimData:
Expand All @@ -47,6 +48,7 @@ def simdata_mech_3d() -> io.SimData:
def simdata_mech_3d_nomesh() -> io.SimData:
sim_data = simdata_mech_3d()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
return sim_data


Expand Down Expand Up @@ -95,7 +97,8 @@ def sens_pos_3d(sim_data: io.SimData) -> dict[str,np.ndarray]:
# (0.0,5.0,5.0), # yz
# (10.0,5.0,5.0),)) # yz

# assert np.allclose(check,sens_pos["cent-cube"]), "Cube coords wrong in mech.sens_pos_3d"
# assert np.allclose(check,sens_pos["cent-cube"]), \
# "Cube coords wrong in mech.sens_pos_3d"

return sens_pos

Expand Down
12 changes: 10 additions & 2 deletions src/pyvale/verif/pointsensscalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def simdata_2d_analytic() -> io.SimData:
def simdata_2d_analytic_nomesh() -> io.SimData:
(sim_data,_) = asd.scalar_linear_2d()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
return sim_data


Expand All @@ -57,6 +58,7 @@ def simdata_3d() -> io.SimData:
def simdata_3d_nomesh() -> io.SimData:
sim_data = simdata_3d()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
return sim_data


Expand Down Expand Up @@ -86,13 +88,19 @@ def sens_pos_3d(sim_data) -> dict[str,np.ndarray]:
x_lims = (sim_dims["x"][1],sim_dims["x"][1])
y_lims = sim_dims["y"]
z_lims = sim_dims["z"]
sens_pos["line-y-yz"] = sens.gen_pos_grid_inside(n_sens,x_lims,y_lims,z_lims)
sens_pos["line-y-yz"] = sens.gen_pos_grid_inside(n_sens,
x_lims,
y_lims,
z_lims)

n_sens = (1,4,1)
x_lims = (9.4,9.4) # Monoblock offset front face
y_lims = sim_dims["y"]
z_lims = (sim_dims["z"][1],sim_dims["z"][1])
sens_pos["line-y-xy"] = sens.gen_pos_grid_inside(n_sens,x_lims,y_lims,z_lims)
sens_pos["line-y-xy"] = sens.gen_pos_grid_inside(n_sens,
x_lims,
y_lims,
z_lims)

return sens_pos

Expand Down
2 changes: 2 additions & 0 deletions src/pyvale/verif/pointsenstensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pyvale.mooseherder as mh
import pyvale.sensorsim as sens
import pyvale.dataio as io
import pyvale.verif.pointsens as pointsens
import pyvale.verif.pointsensmech as pointsensmech
import pyvale.verif.analyticsimdatafactory as asd

Expand All @@ -30,6 +31,7 @@ def simdata_tens_2d_analytic() -> io.SimData:
def simdata_tens_2d_analytic_nomesh() -> io.SimData:
sim_data = simdata_tens_2d_analytic()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
return sim_data

def sens_array_2d_noerrs(sim_data: io.SimData,
Expand Down
2 changes: 2 additions & 0 deletions src/pyvale/verif/pointsensvector.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pyvale.mooseherder as mh
import pyvale.sensorsim as sens
import pyvale.dataio as io
import pyvale.verif.pointsens as pointsens
import pyvale.verif.pointsensmech as pointsensmech
import pyvale.verif.analyticsimdatafactory as asd

Expand All @@ -31,6 +32,7 @@ def simdata_vec_2d_analytic() -> io.SimData:
def simdata_vec_2d_analytic_nomesh() -> io.SimData:
sim_data = simdata_vec_2d_analytic()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
return sim_data


Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-grid-22_time-sim_err-all.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-grid-22_time-sim_err-basic.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-grid-22_time-user_err-all.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-grid-22_time-user_err-basic.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-line-4_time-sim_err-basic.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-line-4_time-sim_err-calib.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal2d_pos-line-4_time-user_err-calib.npy
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal3d_pos-line-y-xy_time-sim_err-all.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal3d_pos-line-y-xy_time-sim_err-basic.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal3d_pos-line-y-xy_time-user_err-all.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal3d_pos-line-y-yz_time-sim_err-all.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/scal3d_pos-line-y-yz_time-user_err-all.npy
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified tests/sensorsim/gold/vec2d_pos-grid-23_time-sim_err-basic.npy
Binary file not shown.
Binary file modified tests/sensorsim/gold/vec2d_pos-line-4_time-sim_err-basic.npy
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
47 changes: 47 additions & 0 deletions tests/sensorsim/test_sensorsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,50 @@ def test_analytic_interp_tensor_nomesh_2d() -> None:
rtol=1e-3)
assert not fails, "\n".join(fails)


def test_joggle_meshfree_coords_invariants() -> None:
coords = np.array([
[0.0, 0.0, 0.0],
[10.0, 0.0, 0.0],
[0.0, 10.0, 0.0],
[10.0, 10.0, 0.0],
[5.0, 5.0, 0.0],
])
joggled1 = pointsens.joggle_meshfree_coords(coords, seed=123)
joggled2 = pointsens.joggle_meshfree_coords(coords, seed=123)
np.testing.assert_array_equal(joggled1, joggled2)

# Verify boundary planes and z=0 are strictly preserved
np.testing.assert_allclose(joggled1[:, 2], 0.0)
assert np.isclose(joggled1[0, 0], 0.0)
assert np.isclose(joggled1[1, 0], 10.0)
assert np.isclose(joggled1[0, 1], 0.0)
assert np.isclose(joggled1[2, 1], 10.0)

# Verify interior node was perturbed
assert abs(joggled1[4, 0] - 5.0) > 1e-12


def test_degenerate_delaunay_invariants() -> None:
(sim_data, _) = asd.scalar_linear_2d()
sim_data.connect = None
sim_data.coords = pointsens.joggle_meshfree_coords(sim_data.coords)
field_interp = sens.FieldInterpPoints(
sim_data=sim_data,
comp_keys=("temperature",),
spatial_dims=sens.EDim.TWOD,
)
# Check evaluation on nodes
sample_nodes = field_interp.interp_field(sim_data.coords)
assert not np.isnan(sample_nodes).any()
# Check evaluation on interior points
sens_grid = sens.gen_pos_grid_inside(
num_sensors=(3, 3, 1),
x_lims=(0.0, 10.0),
y_lims=(0.0, 7.5),
z_lims=(0.0, 0.0),
)
sample_grid = field_interp.interp_field(sens_grid)
assert not np.isnan(sample_grid).any()


Loading