From bc57863420e5b64ed060b53d6907e10aeec2ec40 Mon Sep 17 00:00:00 2001 From: B <42384776+feelingsonice@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:57:34 -0800 Subject: [PATCH] fix: absolute float timestamps --- .../streams/r_py_tick_construction_helper.cpp | 8 ++- tests/streams/test_tick_stream.py | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/roughpy/src/streams/r_py_tick_construction_helper.cpp b/roughpy/src/streams/r_py_tick_construction_helper.cpp index e7b2dd035..8402917b3 100644 --- a/roughpy/src/streams/r_py_tick_construction_helper.cpp +++ b/roughpy/src/streams/r_py_tick_construction_helper.cpp @@ -131,7 +131,13 @@ void python::RPyTickConstructionHelper::add_tick( } else if (data.is_none()) { fail_data_none(); } else { - if (m_reference_time.is_none()) { m_reference_time = timestamp; } + if (m_reference_time.is_none()) { + if (py::isinstance(timestamp)) { + m_reference_time = py::float_(0.0); + } else { + m_reference_time = timestamp; + } + } const auto param = python::convert_delta_from_datetimes( timestamp, m_reference_time, m_time_conversion_options ); diff --git a/tests/streams/test_tick_stream.py b/tests/streams/test_tick_stream.py index 4e094aa10..769c08271 100644 --- a/tests/streams/test_tick_stream.py +++ b/tests/streams/test_tick_stream.py @@ -30,6 +30,7 @@ import pytest +import roughpy as rp from roughpy import DPReal, Lie, RealInterval, TickStream DATA_FORMATS = [ @@ -135,3 +136,70 @@ def test_zero_data_at_zero_timestamp(): sig = stream.signature(RealInterval(0.0, 2.0)) assert sig is not None + + +def test_tick_stream_float_timestamps_are_absolute(): + """ + Regression test for issue where float timestamps were being shifted relative + to the first data point. They should be treated as absolute parameters (reference 0.0). + """ + ctx = rp.get_context(3, 2, DPReal) + + # Simple schema + schema = [ + ("time", "value", {"lead_lag": False}), + ("s1", "value", {"lead_lag": True}), + ] + + # Data starting at t=3.0 + # If timestamps were relative to first point, this would look like starting at t=0.0 + raw_data = [ + (3.0, "s1", "value", 3.0), + (5.0, "s1", "value", 3.0), + ] + + data_with_time = [] + for t, label, typ, val in raw_data: + data_with_time.append((t, "time", "value", t)) + data_with_time.append((t, label, typ, val)) + + support = RealInterval(0, 7) + + s1 = TickStream.from_data( + data_with_time, + schema=schema, + ctx=ctx, + support=support, + ) + + # Query interval [0, 2). Since data starts at 3.0, this should be empty/trivial. + interval_empty = RealInterval(0.0, 2.0) + sig_empty = s1.signature(interval_empty, ctx=ctx, resolution=10) + + # Should be identity (1.0) + # Checking if it has only one component (scalar) and it is 1.0 + # Or just check size if it's sparse? + # But FreeTensor might not be sparse in python binding access + + # We can check specific coefficients + # The scalar component (empty word) should be 1.0 + # All other components should be 0.0 + + # In RoughPy, converting to list/dict helps. + # sig_empty should be effectively 1. + + # Using str(sig_empty) to check + assert str(sig_empty) == "{ 1() }" + + # Query interval [0, 4) which contains the first jump at 3.0. + interval_first = RealInterval(0.0, 4.0) + sig_first = s1.signature(interval_first, ctx=ctx, resolution=10) + + # Should NOT be identity + assert str(sig_first) != "{ 1() }" + + # Check that it captured the jump + # The jump is (3, 3, 3) at t=3.0. + # We can check that the "time" channel (1) increment is 3.0 + # Note: accessing coefficients might need specific API. + # But string representation check is robust enough for regression here.