feat(eval): publish cycle-mean wake acquisition
Add deterministic phase-filtered V5 and Legacy acquisition with complete-cycle mean fields, then evaluate controlled wakes against target and zero baselines offline. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,8 +9,11 @@ import numpy as np
|
||||
import pytest
|
||||
|
||||
from drl_pinball.acquisition import (
|
||||
accumulate_mean_fields,
|
||||
assign_phase,
|
||||
assign_periodic_phase,
|
||||
cleanup_scratch,
|
||||
complete_cycle_field_indices,
|
||||
create_scratch,
|
||||
default_reproduction_mapping,
|
||||
decode_legacy_physical_velocity,
|
||||
@@ -20,6 +23,7 @@ from drl_pinball.acquisition import (
|
||||
publish_role_output,
|
||||
publish_selected_fields,
|
||||
select_phase_fields,
|
||||
smooth_center_uy,
|
||||
validate_output_storage,
|
||||
validate_modern_fields,
|
||||
write_boundary_artifacts,
|
||||
@@ -184,6 +188,59 @@ def test_selection_is_global_eight_with_metadata_and_index_tie():
|
||||
assert np.allclose(result["phase_error"], [0.0] * 8, atol=1e-15)
|
||||
|
||||
|
||||
def test_smooth_center_uy_binomial_and_endpoint_copy():
|
||||
assert np.allclose(smooth_center_uy([1.0, 5.0, 1.0]), [1.0, 3.0, 1.0])
|
||||
assert np.allclose(smooth_center_uy([2.0, 4.0]), [2.0, 4.0])
|
||||
|
||||
|
||||
def test_assign_phase_default_behavior_unchanged_with_jitter():
|
||||
times = np.arange(12, dtype=float)
|
||||
sensors = np.zeros((12, 6))
|
||||
sensors[:, 3] = [-1, 1, -0.1, 0.1, -1, 1, -1, 1, -1, 1, -1, 1]
|
||||
result = assign_phase(times, sensors)
|
||||
assert len(result["crossing_times"]) == 6
|
||||
assert set(result) == {"phase", "cycle_id", "crossing_times"}
|
||||
|
||||
|
||||
def test_assign_periodic_phase_filters_jitter_with_minimum_interval():
|
||||
times = np.arange(20, dtype=float)
|
||||
sensors = np.zeros((20, 6))
|
||||
# Deep brief negative between rises so smoothing keeps a close false crossing.
|
||||
sensors[:, 3] = [-2, -2, -1, 1, 2, -3, 1, 2, 2, -2, -1, 1, 2, 2, -2, -1, 1, 2, 2, -2]
|
||||
raw = assign_phase(times, sensors)
|
||||
filtered = assign_periodic_phase(times, sensors, minimum_crossing_interval=4.0)
|
||||
assert len(raw["crossing_times"]) >= 4
|
||||
assert filtered["rejected_crossing_count"] >= 1
|
||||
assert 5.75 in filtered["rejected_crossing_times"]
|
||||
assert filtered["accepted_crossing_count"] == len(filtered["crossing_times"])
|
||||
assert filtered["complete_cycle_count"] == filtered["accepted_crossing_count"] - 1
|
||||
assert np.all(np.diff(filtered["crossing_times"]) >= 4.0 - 1e-12)
|
||||
assert filtered["smoothing_kernel"] == [0.25, 0.5, 0.25]
|
||||
first, last = filtered["crossing_times"][0], filtered["crossing_times"][-1]
|
||||
valid = (times >= first) & (times < last)
|
||||
assert np.all(filtered["cycle_id"][valid] >= 0)
|
||||
assert np.all(filtered["cycle_id"][~valid] < 0)
|
||||
|
||||
|
||||
def test_complete_cycle_mean_excludes_outside_first_last_crossing():
|
||||
times = np.arange(10, dtype=float)
|
||||
crossings = np.array([2.0, 6.0, 9.0])
|
||||
selected = complete_cycle_field_indices(times, crossings)
|
||||
assert selected["field_indices"].tolist() == [2, 3, 4, 5, 6, 7, 8]
|
||||
assert selected["mean_field_count"] == 7
|
||||
assert selected["complete_cycle_count"] == 2
|
||||
ux = np.arange(10, dtype=np.float32).reshape(10, 1, 1)
|
||||
uy = (2 * np.arange(10)).astype(np.float32).reshape(10, 1, 1)
|
||||
mean = accumulate_mean_fields(ux, uy, selected["field_indices"])
|
||||
assert mean["mean_ux"].shape == (1, 1)
|
||||
assert mean["mean_uy"].shape == (1, 1)
|
||||
assert float(mean["mean_ux"][0, 0]) == pytest.approx(5.0)
|
||||
assert float(mean["mean_uy"][0, 0]) == pytest.approx(10.0)
|
||||
# Eight-slot average of selected phase snapshots must remain a different concept.
|
||||
eight = accumulate_mean_fields(ux, uy, np.array([2, 3, 4, 5, 6, 7, 8, 8]))
|
||||
assert float(eight["mean_ux"][0, 0]) != pytest.approx(float(mean["mean_ux"][0, 0]))
|
||||
|
||||
|
||||
def test_pooled_32_bins_population_std_and_original_indices():
|
||||
result = pooled_phase_bins(
|
||||
np.array([np.pi / 32, 3 * np.pi / 32, np.pi / 32, np.nan]),
|
||||
|
||||
Reference in New Issue
Block a user