• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17920603410

22 Sep 2025 03:39PM UTC coverage: 71.97% (-0.05%) from 72.02%
17920603410

push

github

paulmthompson
all tests pass

277 of 288 new or added lines in 8 files covered. (96.18%)

520 existing lines in 35 files now uncovered.

40275 of 55961 relevant lines covered (71.97%)

1225.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.14
/src/DataManager/utils/TableView/computers/EventInIntervalComputer.test.cpp
1
#include <catch2/catch_test_macros.hpp>
2
#include <catch2/catch_approx.hpp>
3

4
#include "EventInIntervalComputer.h"
5
#include "utils/TableView/core/ExecutionPlan.h"
6
#include "utils/TableView/interfaces/IEventSource.h"
7
#include "TimeFrame/interval_data.hpp"
8
#include "TimeFrame/TimeFrame.hpp"
9

10
// Additional includes for extended testing
11
#include "DataManager.hpp"
12
#include "utils/TableView/ComputerRegistry.hpp"
13
#include "utils/TableView/adapters/DataManagerExtension.h"
14
#include "utils/TableView/core/TableView.h"
15
#include "utils/TableView/core/TableViewBuilder.h"
16
#include "utils/TableView/interfaces/IRowSelector.h"
17
#include "utils/TableView/interfaces/IIntervalSource.h"
18
#include "utils/TableView/pipeline/TablePipeline.hpp"
19
#include "utils/TableView/TableRegistry.hpp"
20
#include "DigitalTimeSeries/Digital_Event_Series.hpp"
21
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
22

23
#include <memory>
24
#include <vector>
25
#include <algorithm>
26
#include <span>
27
#include <cstdint>
28
#include <numeric>
29
#include <nlohmann/json.hpp>
30

31
// Mock implementation of IEventSource for testing
32
class MockEventSource : public IEventSource {
33
public:
34
    MockEventSource(std::string name, 
12✔
35
                    std::shared_ptr<TimeFrame> timeFrame,
36
                    std::vector<float> events)
37
        : m_name(std::move(name)), 
12✔
38
          m_timeFrame(std::move(timeFrame)), 
12✔
39
          m_events(std::move(events)) {
24✔
40
        // Ensure events are sorted
41
        std::sort(m_events.begin(), m_events.end());
12✔
42
    }
12✔
43

44
    [[nodiscard]] auto getName() const -> std::string const & override {
×
45
        return m_name;
×
46
    }
47

48
    [[nodiscard]] auto getTimeFrame() const -> std::shared_ptr<TimeFrame> override {
3✔
49
        return m_timeFrame;
3✔
50
    }
51

52
    [[nodiscard]] auto size() const -> size_t override {
×
53
        return m_events.size();
×
54
    }
55

UNCOV
56
    auto getDataInRange(TimeFrameIndex start, TimeFrameIndex end, 
×
57
                       TimeFrame const * target_timeFrame) -> std::vector<float> override {
UNCOV
58
        std::vector<float> result;
×
59
        
60
        // Convert TimeFrameIndex to time values for comparison
UNCOV
61
        auto startTime = target_timeFrame->getTimeAtIndex(start);
×
UNCOV
62
        auto endTime = target_timeFrame->getTimeAtIndex(end);
×
63
        
UNCOV
64
        for (const auto& event : m_events) {
×
65
            // Convert event to time value using our timeframe
66
            // For simplicity, assume events are already time values
UNCOV
67
            if (event >= startTime && event <= endTime) {
×
UNCOV
68
                result.push_back(event);
×
69
            }
70
        }
71
        
UNCOV
72
        return result;
×
73
    }
×
74

75
    auto getDataInRangeWithEntityIds(TimeFrameIndex start, TimeFrameIndex end, 
80✔
76
                                    TimeFrame const * target_timeFrame) -> std::vector<EventWithId> override {
77
        std::vector<EventWithId> result;
80✔
78
        
79
        // Convert TimeFrameIndex to time values for comparison
80
        auto startTime = target_timeFrame->getTimeAtIndex(start);
80✔
81
        auto endTime = target_timeFrame->getTimeAtIndex(end);
80✔
82
        
83
        for (const auto& event : m_events) {
1,393✔
84
            // Convert event to time value using our timeframe
85
            // For simplicity, assume events are already time values
86
            if (event >= startTime && event <= endTime) {
1,313✔
87
                result.push_back(EventWithId(event, 0));
118✔
88
            }
89
        }
90
        
91
        return result;
80✔
UNCOV
92
    }
×
93

94
private:
95
    std::string m_name;
96
    std::shared_ptr<TimeFrame> m_timeFrame;
97
    std::vector<float> m_events;
98
};
99

100
/**
101
 * @brief Base test fixture for EventInIntervalComputer with realistic event data
102
 * 
103
 * This fixture provides a DataManager populated with:
104
 * - TimeFrames with different granularities
105
 * - Row intervals representing behavior periods  
106
 * - Event data representing spike times or other discrete events
107
 * - Cross-timeframe events for testing timeframe conversion
108
 */
109
class EventInIntervalTestFixture {
110
protected:
111
    EventInIntervalTestFixture() {
11✔
112
        // Initialize the DataManager
113
        m_data_manager = std::make_unique<DataManager>();
11✔
114
        
115
        // Populate with test data
116
        populateWithEventTestData();
11✔
117
    }
11✔
118

119
    ~EventInIntervalTestFixture() = default;
11✔
120

121
    /**
122
     * @brief Get the DataManager instance
123
     */
124
    DataManager & getDataManager() { return *m_data_manager; }
26✔
125
    DataManager const & getDataManager() const { return *m_data_manager; }
126
    DataManager * getDataManagerPtr() { return m_data_manager.get(); }
127

128
private:
129
    std::unique_ptr<DataManager> m_data_manager;
130

131
    /**
132
     * @brief Populate the DataManager with event test data
133
     */
134
    void populateWithEventTestData() {
11✔
135
        createTimeFrames();
11✔
136
        createBehaviorIntervals();
11✔
137
        createSpikeEvents();
11✔
138
    }
11✔
139

140
    /**
141
     * @brief Create TimeFrame objects for different data streams
142
     */
143
    void createTimeFrames() {
11✔
144
        // Create "behavior_time" timeframe: 0 to 100 (101 points) - behavior tracking at 10Hz
145
        std::vector<int> behavior_time_values(101);
33✔
146
        std::iota(behavior_time_values.begin(), behavior_time_values.end(), 0);
11✔
147
        auto behavior_time_frame = std::make_shared<TimeFrame>(behavior_time_values);
11✔
148
        m_data_manager->setTime(TimeKey("behavior_time"), behavior_time_frame, true);
11✔
149

150
        // Create "spike_time" timeframe: 0, 2, 4, 6, ..., 100 (51 points) - spike recording at 5Hz
151
        std::vector<int> spike_time_values;
11✔
152
        spike_time_values.reserve(51);
11✔
153
        for (int i = 0; i <= 50; ++i) {
572✔
154
            spike_time_values.push_back(i * 2);
561✔
155
        }
156
        auto spike_time_frame = std::make_shared<TimeFrame>(spike_time_values);
11✔
157
        m_data_manager->setTime(TimeKey("spike_time"), spike_time_frame, true);
11✔
158
    }
22✔
159

160
    /**
161
     * @brief Create behavior intervals (row intervals for testing)
162
     */
163
    void createBehaviorIntervals() {
11✔
164
        // Create behavior periods: exploration, rest, exploration
165
        auto behavior_intervals = std::make_shared<DigitalIntervalSeries>();
11✔
166

167
        // Exploration period 1: time 10-25
168
        behavior_intervals->addEvent(TimeFrameIndex(10), TimeFrameIndex(25));
11✔
169
        
170
        // Rest period: time 30-40  
171
        behavior_intervals->addEvent(TimeFrameIndex(30), TimeFrameIndex(40));
11✔
172
        
173
        // Exploration period 2: time 50-70
174
        behavior_intervals->addEvent(TimeFrameIndex(50), TimeFrameIndex(70));
11✔
175
        
176
        // Social interaction: time 80-95
177
        behavior_intervals->addEvent(TimeFrameIndex(80), TimeFrameIndex(95));
11✔
178

179
        m_data_manager->setData<DigitalIntervalSeries>("BehaviorPeriods", behavior_intervals, TimeKey("behavior_time"));
33✔
180
    }
22✔
181

182
    /**
183
     * @brief Create spike event data (event data for testing)
184
     */
185
    void createSpikeEvents() {
11✔
186
        // Create spike train for Neuron1 - sparse spikes
187
        // Note: spike_time timeframe has 51 values [0, 2, 4, 6, ..., 100]
188
        // Events store INDICES into this timeframe, not absolute time values
189
        // So spike event "5" means timeframe[5] = 10 (absolute time)
190
        std::vector<float> neuron1_spikes = {
11✔
191
            1.0f,   // index 1 → time 2
192
            6.0f,   // index 6 → time 12
193
            7.0f,   // index 7 → time 14
194
            11.0f,  // index 11 → time 22
195
            16.0f,  // index 16 → time 32
196
            26.0f,  // index 26 → time 52
197
            27.0f,  // index 27 → time 54
198
            34.0f,  // index 34 → time 68
199
            41.0f,  // index 41 → time 82
200
            45.0f   // index 45 → time 90
201
        };
33✔
202
        auto neuron1_series = std::make_shared<DigitalEventSeries>(neuron1_spikes);
11✔
203
        m_data_manager->setData<DigitalEventSeries>("Neuron1Spikes", neuron1_series, TimeKey("spike_time"));
33✔
204
        
205
        // Initialize EntityIDs for neuron1 spike series
206
        neuron1_series->setIdentityContext("Neuron1Spikes", m_data_manager->getEntityRegistry());
33✔
207
        neuron1_series->rebuildAllEntityIds();
11✔
208
        
209
        // Create spike train for Neuron2 - dense spikes
210
        // All values are indices into the spike timeframe
211
        std::vector<float> neuron2_spikes = {
11✔
212
            0.0f,   // index 0 → time 0
213
            1.0f,   // index 1 → time 2
214
            2.0f,   // index 2 → time 4
215
            5.0f,   // index 5 → time 10
216
            6.0f,   // index 6 → time 12
217
            8.0f,   // index 8 → time 16
218
            9.0f,   // index 9 → time 18
219
            15.0f,  // index 15 → time 30
220
            16.0f,  // index 16 → time 32
221
            18.0f,  // index 18 → time 36
222
            25.0f,  // index 25 → time 50
223
            26.0f,  // index 26 → time 52
224
            28.0f,  // index 28 → time 56
225
            29.0f,  // index 29 → time 58
226
            33.0f,  // index 33 → time 66
227
            34.0f,  // index 34 → time 68
228
            40.0f,  // index 40 → time 80
229
            41.0f,  // index 41 → time 82
230
            42.0f,  // index 42 → time 84
231
            45.0f,  // index 45 → time 90
232
            46.0f   // index 46 → time 92
233
        };
33✔
234
        auto neuron2_series = std::make_shared<DigitalEventSeries>(neuron2_spikes);
11✔
235
        m_data_manager->setData<DigitalEventSeries>("Neuron2Spikes", neuron2_series, TimeKey("spike_time"));
33✔
236
        
237
        // Initialize EntityIDs for neuron2 spike series
238
        neuron2_series->setIdentityContext("Neuron2Spikes", m_data_manager->getEntityRegistry());
33✔
239
        neuron2_series->rebuildAllEntityIds();
11✔
240
        
241
        // Create spike train for Neuron3 - rhythmic spikes every 16 time units
242
        // Starting at time 4 (index 2), then time 12 (index 6), time 20 (index 10), etc.
243
        std::vector<float> neuron3_spikes;
11✔
244
        for (int i = 2; i <= 48; i += 4) {  // indices 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46
143✔
245
            neuron3_spikes.push_back(static_cast<float>(i));
132✔
246
        }
247
        auto neuron3_series = std::make_shared<DigitalEventSeries>(neuron3_spikes);
11✔
248
        m_data_manager->setData<DigitalEventSeries>("Neuron3Spikes", neuron3_series, TimeKey("spike_time"));
33✔
249
        
250
        // Initialize EntityIDs for neuron3 spike series
251
        neuron3_series->setIdentityContext("Neuron3Spikes", m_data_manager->getEntityRegistry());
33✔
252
        neuron3_series->rebuildAllEntityIds();
11✔
253
    }
22✔
254
};
255

256
/**
257
 * @brief Test fixture combining EventInIntervalTestFixture with TableRegistry and TablePipeline
258
 * 
259
 * This fixture provides everything needed to test JSON-based table pipeline execution:
260
 * - DataManager with event test data (from EventInIntervalTestFixture)
261
 * - TableRegistry for managing table configurations
262
 * - TablePipeline for executing JSON configurations
263
 */
264
class EventTableRegistryTestFixture : public EventInIntervalTestFixture {
265
protected:
266
    EventTableRegistryTestFixture()
9✔
267
        : EventInIntervalTestFixture() {
9✔
268
        // Use the DataManager's existing TableRegistry instead of creating a new one
269
        m_table_registry_ptr = getDataManager().getTableRegistry();
9✔
270

271
        // Initialize TablePipeline with the existing TableRegistry
272
        m_table_pipeline = std::make_unique<TablePipeline>(m_table_registry_ptr, &getDataManager());
9✔
273
    }
9✔
274

275
    ~EventTableRegistryTestFixture() = default;
9✔
276

277
    /**
278
     * @brief Get the TableRegistry instance
279
     * @return Reference to the TableRegistry
280
     */
281
    TableRegistry & getTableRegistry() { return *m_table_registry_ptr; }
5✔
282

283
    /**
284
     * @brief Get the TableRegistry instance (const version)
285
     * @return Const reference to the TableRegistry
286
     */
287
    TableRegistry const & getTableRegistry() const { return *m_table_registry_ptr; }
288

289
    /**
290
     * @brief Get a pointer to the TableRegistry
291
     * @return Raw pointer to the TableRegistry
292
     */
293
    TableRegistry * getTableRegistryPtr() { return m_table_registry_ptr; }
294

295
    /**
296
     * @brief Get the TablePipeline instance
297
     * @return Reference to the TablePipeline
298
     */
299
    TablePipeline & getTablePipeline() { return *m_table_pipeline; }
2✔
300

301
    /**
302
     * @brief Get the TablePipeline instance (const version)
303
     * @return Const reference to the TablePipeline
304
     */
305
    TablePipeline const & getTablePipeline() const { return *m_table_pipeline; }
306

307
    /**
308
     * @brief Get a pointer to the TablePipeline
309
     * @return Raw pointer to the TablePipeline
310
     */
311
    TablePipeline * getTablePipelinePtr() { return m_table_pipeline.get(); }
312

313
    /**
314
     * @brief Get the DataManagerExtension instance
315
     */
316
    std::shared_ptr<DataManagerExtension> getDataManagerExtension() { 
317
        if (!m_data_manager_extension) {
318
            m_data_manager_extension = std::make_shared<DataManagerExtension>(getDataManager());
319
        }
320
        return m_data_manager_extension; 
321
    }
322

323
private:
324
    TableRegistry * m_table_registry_ptr; // Points to DataManager's TableRegistry
325
    std::unique_ptr<TablePipeline> m_table_pipeline;
326
    std::shared_ptr<DataManagerExtension> m_data_manager_extension; // Lazy-initialized
327
};
328

329
TEST_CASE("DM - TV - EventInIntervalComputer Basic Functionality", "[EventInIntervalComputer]") {
3✔
330
    
331
    SECTION("Presence operation - detect events in intervals") {
3✔
332
        // Create time frame
333
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
3✔
334
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
335
        
336
        // Create event data (events at times 1, 3, 5, 7, 9)
337
        std::vector<float> events = {1.0f, 3.0f, 5.0f, 7.0f, 9.0f};
3✔
338
        
339
        auto eventSource = std::make_shared<MockEventSource>(
1✔
340
            "TestEvents", timeFrame, events);
1✔
341
        
342
        // Create intervals for testing
343
        std::vector<TimeFrameInterval> intervals = {
1✔
344
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Time 0-2: contains event at 1
345
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4)), // Time 2-4: contains event at 3
346
            TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(6)), // Time 4-6: contains event at 5
347
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(8)), // Time 6-8: contains event at 7
348
            TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(10)), // Time 8-10: contains event at 9
349
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(1)),  // Time 1-1: contains event at 1
350
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(6))   // Time 6-6: no events
351
        };
3✔
352
        
353
        ExecutionPlan plan(intervals, timeFrame);
1✔
354
        
355
        // Create the computer
356
        EventInIntervalComputer<bool> computer(eventSource, 
1✔
357
                                              EventOperation::Presence,
358
                                              "TestEvents");
3✔
359
        
360
        // Compute the results
361
        auto [results, entity_ids] = computer.compute(plan);
1✔
362
        
363
        // Verify results
364
        REQUIRE(results.size() == 7);
1✔
365
        REQUIRE(results[0] == true);   // Interval 0-2 contains event at 1
1✔
366
        REQUIRE(results[1] == true);   // Interval 2-4 contains event at 3
1✔
367
        REQUIRE(results[2] == true);   // Interval 4-6 contains event at 5
1✔
368
        REQUIRE(results[3] == true);   // Interval 6-8 contains event at 7
1✔
369
        REQUIRE(results[4] == true);   // Interval 8-10 contains event at 9
1✔
370
        REQUIRE(results[5] == true);   // Interval 1-1 contains event at 1
1✔
371
        REQUIRE(results[6] == false);  // Interval 6-6 contains no events
1✔
372
    }
4✔
373
    
374
    SECTION("Count operation - count events in intervals") {
3✔
375
        // Create time frame
376
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
3✔
377
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
378
        
379
        // Create event data with multiple events in some intervals
380
        std::vector<float> events = {1.0f, 1.5f, 3.0f, 5.0f, 5.5f, 5.8f, 7.0f, 9.0f};
3✔
381
        
382
        auto eventSource = std::make_shared<MockEventSource>(
1✔
383
            "TestEvents", timeFrame, events);
1✔
384
        
385
        // Create intervals for testing
386
        std::vector<TimeFrameInterval> intervals = {
1✔
387
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Time 0-2: events at 1.0, 1.5
388
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4)), // Time 2-4: event at 3.0
389
            TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(6)), // Time 4-6: events at 5.0, 5.5, 5.8
390
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(8)), // Time 6-8: event at 7.0
391
            TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(10)), // Time 8-10: event at 9.0
392
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(6))   // Time 6-6: no events
393
        };
3✔
394
        
395
        ExecutionPlan plan(intervals, timeFrame);
1✔
396
        
397
        // Create the computer
398
        EventInIntervalComputer<int> computer(eventSource, 
1✔
399
                                             EventOperation::Count,
400
                                             "TestEvents");
3✔
401
        
402
        // Compute the results
403
        auto [results, entity_ids] = computer.compute(plan);
1✔
404
        
405
        // Verify results
406
        REQUIRE(results.size() == 6);
1✔
407
        REQUIRE(results[0] == 2);  // Interval 0-2: 2 events
1✔
408
        REQUIRE(results[1] == 1);  // Interval 2-4: 1 event
1✔
409
        REQUIRE(results[2] == 3);  // Interval 4-6: 3 events
1✔
410
        REQUIRE(results[3] == 1);  // Interval 6-8: 1 event
1✔
411
        REQUIRE(results[4] == 1);  // Interval 8-10: 1 event
1✔
412
        REQUIRE(results[5] == 0);  // Interval 6-6: 0 events
1✔
413
    }
4✔
414
    
415
    SECTION("Gather operation - collect events in intervals") {
3✔
416
        // Create time frame
417
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
3✔
418
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
419
        
420
        // Create event data
421
        std::vector<float> events = {1.0f, 2.5f, 3.0f, 5.0f, 5.5f, 7.0f, 9.0f};
3✔
422
        
423
        auto eventSource = std::make_shared<MockEventSource>(
1✔
424
            "TestEvents", timeFrame, events);
1✔
425
        
426
        // Create intervals for testing
427
        std::vector<TimeFrameInterval> intervals = {
1✔
428
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(3)), // Time 0-3: events at 1.0, 2.5, 3.0
429
            TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(6)), // Time 4-6: events at 5.0, 5.5
430
            TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(10)), // Time 8-10: event at 9.0
431
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(6))   // Time 6-6: no events
432
        };
3✔
433
        
434
        ExecutionPlan plan(intervals, timeFrame);
1✔
435
        
436
        // Create the computer
437
        EventInIntervalComputer<std::vector<float>> computer(eventSource, 
1✔
438
                                                            EventOperation::Gather,
439
                                                            "TestEvents");
3✔
440
        
441
        // Compute the results
442
        auto [results, entity_ids] = computer.compute(plan);
1✔
443
        
444
        // Verify results
445
        REQUIRE(results.size() == 4);
1✔
446
        
447
        // Check first interval (0-3)
448
        REQUIRE(results[0].size() == 3);
1✔
449
        REQUIRE(results[0][0] == Catch::Approx(1.0f).epsilon(0.001f));
1✔
450
        REQUIRE(results[0][1] == Catch::Approx(2.5f).epsilon(0.001f));
1✔
451
        REQUIRE(results[0][2] == Catch::Approx(3.0f).epsilon(0.001f));
1✔
452
        
453
        // Check second interval (4-6)
454
        REQUIRE(results[1].size() == 2);
1✔
455
        REQUIRE(results[1][0] == Catch::Approx(5.0f).epsilon(0.001f));
1✔
456
        REQUIRE(results[1][1] == Catch::Approx(5.5f).epsilon(0.001f));
1✔
457
        
458
        // Check third interval (8-10)
459
        REQUIRE(results[2].size() == 1);
1✔
460
        REQUIRE(results[2][0] == Catch::Approx(9.0f).epsilon(0.001f));
1✔
461
        
462
        // Check fourth interval (6-6) - should be empty
463
        REQUIRE(results[3].size() == 0);
1✔
464
    }
4✔
465
}
3✔
466

467
TEST_CASE("DM - TV - EventInIntervalComputer Edge Cases", "[EventInIntervalComputer][EdgeCases]") {
3✔
468
    
469
    SECTION("Empty event source") {
3✔
470
        // Create time frame
471
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
472
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
473
        
474
        // Create empty event data
475
        std::vector<float> events;
1✔
476
        
477
        auto eventSource = std::make_shared<MockEventSource>(
1✔
478
            "EmptyEvents", timeFrame, events);
1✔
479
        
480
        // Create intervals for testing
481
        std::vector<TimeFrameInterval> intervals = {
1✔
482
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)),
483
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4))
484
        };
3✔
485
        
486
        ExecutionPlan plan(intervals, timeFrame);
1✔
487
        
488
        // Test Presence operation
489
        EventInIntervalComputer<bool> presenceComputer(eventSource, 
1✔
490
                                                      EventOperation::Presence,
491
                                                      "EmptyEvents");
3✔
492
        
493
        auto [presenceResults, presenceEntity_ids] = presenceComputer.compute(plan);
1✔
494
        
495
        REQUIRE(presenceResults.size() == 2);
1✔
496
        REQUIRE(presenceResults[0] == false);
1✔
497
        REQUIRE(presenceResults[1] == false);
1✔
498
        
499
        // Test Count operation
500
        EventInIntervalComputer<int> countComputer(eventSource, 
1✔
501
                                                  EventOperation::Count,
502
                                                  "EmptyEvents");
3✔
503
        
504
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
505
        
506
        REQUIRE(countResults.size() == 2);
1✔
507
        REQUIRE(countResults[0] == 0);
1✔
508
        REQUIRE(countResults[1] == 0);
1✔
509
        
510
        // Test Gather operation
511
        EventInIntervalComputer<std::vector<float>> gatherComputer(eventSource, 
1✔
512
                                                                  EventOperation::Gather,
513
                                                                  "EmptyEvents");
3✔
514
        
515
        auto [gatherResults, gatherEntity_ids] = gatherComputer.compute(plan);
1✔
516
        
517
        REQUIRE(gatherResults.size() == 2);
1✔
518
        REQUIRE(gatherResults[0].size() == 0);
1✔
519
        REQUIRE(gatherResults[1].size() == 0);
1✔
520
    }
4✔
521
    
522
    SECTION("Single event scenarios") {
3✔
523
        // Create time frame
524
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
525
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
526
        
527
        // Create single event data
528
        std::vector<float> events = {2.5f};
3✔
529
        
530
        auto eventSource = std::make_shared<MockEventSource>(
1✔
531
            "SingleEvent", timeFrame, events);
1✔
532
        
533
        // Create intervals for testing
534
        std::vector<TimeFrameInterval> intervals = {
1✔
535
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Before event
536
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)), // Contains event
537
            TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(5))  // After event
538
        };
3✔
539
        
540
        ExecutionPlan plan(intervals, timeFrame);
1✔
541
        
542
        // Test Presence operation
543
        EventInIntervalComputer<bool> presenceComputer(eventSource, 
1✔
544
                                                      EventOperation::Presence,
545
                                                      "SingleEvent");
3✔
546
        
547
        auto [presenceResults, presenceEntity_ids] = presenceComputer.compute(plan);
1✔
548
        
549
        REQUIRE(presenceResults.size() == 3);
1✔
550
        REQUIRE(presenceResults[0] == false);  // Before event
1✔
551
        REQUIRE(presenceResults[1] == true);   // Contains event
1✔
552
        REQUIRE(presenceResults[2] == false);  // After event
1✔
553
        
554
        // Test Count operation
555
        EventInIntervalComputer<int> countComputer(eventSource, 
1✔
556
                                                  EventOperation::Count,
557
                                                  "SingleEvent");
3✔
558
        
559
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
560
        
561
        REQUIRE(countResults.size() == 3);
1✔
562
        REQUIRE(countResults[0] == 0);  // Before event
1✔
563
        REQUIRE(countResults[1] == 1);  // Contains event
1✔
564
        REQUIRE(countResults[2] == 0);  // After event
1✔
565
    }
4✔
566
    
567
    SECTION("Zero-length intervals") {
3✔
568
        // Create time frame
569
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
570
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
571
        
572
        // Create event data
573
        std::vector<float> events = {1.0f, 2.0f, 3.0f, 4.0f};
3✔
574
        
575
        auto eventSource = std::make_shared<MockEventSource>(
1✔
576
            "TestEvents", timeFrame, events);
1✔
577
        
578
        // Create zero-length intervals
579
        std::vector<TimeFrameInterval> intervals = {
1✔
580
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(1)), // Exactly at event
581
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(2)), // Exactly at event
582
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(0))  // Between events
583
        };
3✔
584
        
585
        ExecutionPlan plan(intervals, timeFrame);
1✔
586
        
587
        // Test Presence operation
588
        EventInIntervalComputer<bool> presenceComputer(eventSource, 
1✔
589
                                                      EventOperation::Presence,
590
                                                      "TestEvents");
3✔
591
        
592
        auto [presenceResults, presenceEntity_ids] = presenceComputer.compute(plan);
1✔
593
        
594
        REQUIRE(presenceResults.size() == 3);
1✔
595
        REQUIRE(presenceResults[0] == true);   // At event time 1
1✔
596
        REQUIRE(presenceResults[1] == true);   // At event time 2
1✔
597
        REQUIRE(presenceResults[2] == false);  // At time 0, no event
1✔
598
    }
4✔
599
}
3✔
600

601
TEST_CASE("DM - TV - EventInIntervalComputer Error Handling", "[EventInIntervalComputer][Error]") {
1✔
602
    
603
    SECTION("Wrong operation type for template specialization") {
1✔
604
        // Create minimal setup
605
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
606
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
607
        
608
        std::vector<float> events = {1.0f, 2.0f, 3.0f};
3✔
609
        auto eventSource = std::make_shared<MockEventSource>(
1✔
610
            "TestEvents", timeFrame, events);
1✔
611
        
612
        std::vector<TimeFrameInterval> intervals = {
1✔
613
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2))
614
        };
3✔
615
        
616
        ExecutionPlan plan(intervals, timeFrame);
1✔
617
        
618
        // Test using bool template with wrong operation
619
        EventInIntervalComputer<bool> wrongPresenceComputer(eventSource, 
1✔
620
                                                           EventOperation::Count,  // Wrong operation
621
                                                           "TestEvents");
3✔
622
        
623
        REQUIRE_THROWS_AS(wrongPresenceComputer.compute(plan), std::runtime_error);
2✔
624
        
625
        // Test using int template with wrong operation
626
        EventInIntervalComputer<int> wrongCountComputer(eventSource, 
1✔
627
                                                       EventOperation::Presence,  // Wrong operation
628
                                                       "TestEvents");
3✔
629
        
630
        REQUIRE_THROWS_AS(wrongCountComputer.compute(plan), std::runtime_error);
2✔
631
        
632
        // Test using vector template with wrong operation
633
        EventInIntervalComputer<std::vector<float>> wrongGatherComputer(eventSource, 
1✔
634
                                                                       EventOperation::Count,  // Wrong operation
635
                                                                       "TestEvents");
3✔
636
        
637
        REQUIRE_THROWS_AS(wrongGatherComputer.compute(plan), std::runtime_error);
2✔
638
    }
2✔
639
    
640
}
1✔
641

642
TEST_CASE("DM - TV - EventInIntervalComputer Dependency Tracking", "[EventInIntervalComputer][Dependencies]") {
1✔
643
    
644
    SECTION("getSourceDependency returns correct source name") {
1✔
645
        // Create minimal setup
646
        std::vector<int> timeValues = {0, 1, 2};
3✔
647
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
648
        
649
        std::vector<float> events = {1.0f};
3✔
650
        auto eventSource = std::make_shared<MockEventSource>(
1✔
651
            "TestSource", timeFrame, events);
1✔
652
        
653
        // Create computer
654
        EventInIntervalComputer<bool> computer(eventSource, 
1✔
655
                                              EventOperation::Presence,
656
                                              "TestSourceName");
3✔
657
        
658
        // Test source dependency
659
        REQUIRE(computer.getSourceDependency() == "TestSourceName");
1✔
660
    }
2✔
661
}
1✔
662

663
TEST_CASE_METHOD(EventInIntervalTestFixture, "DM - TV - EventInIntervalComputer with DataManager fixture", "[EventInIntervalComputer][DataManager][Fixture]") {
2✔
664
    
665
    SECTION("Test with behavior periods and spike events from fixture") {
2✔
666
        auto& dm = getDataManager();
1✔
667
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
668
        
669
        // Get the event sources from the DataManager
670
        auto neuron1_source = dme->getEventSource("Neuron1Spikes");
3✔
671
        auto neuron2_source = dme->getEventSource("Neuron2Spikes");
3✔
672
        auto neuron3_source = dme->getEventSource("Neuron3Spikes");
3✔
673
        
674
        REQUIRE(neuron1_source != nullptr);
1✔
675
        REQUIRE(neuron2_source != nullptr);
1✔
676
        REQUIRE(neuron3_source != nullptr);
1✔
677
        
678
        // Get the behavior intervals to use as row selector
679
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
680
        REQUIRE(behavior_source != nullptr);
1✔
681
        
682
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
683
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
684
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
685
        
686
        REQUIRE(behavior_intervals.size() == 4); // 4 behavior periods
1✔
687
        
688
        // Convert to TimeFrameIntervals for row selector
689
        std::vector<TimeFrameInterval> row_intervals;
1✔
690
        for (const auto& interval : behavior_intervals) {
5✔
691
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
692
        }
693
        
694
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
695
        
696
        // Create TableView builder
697
        TableViewBuilder builder(dme);
1✔
698
        builder.setRowSelector(std::move(row_selector));
1✔
699
        
700
        // Test Presence operation
701
        builder.addColumn<bool>("Neuron1_Present", 
5✔
702
            std::make_unique<EventInIntervalComputer<bool>>(neuron1_source, 
3✔
703
                EventOperation::Presence, "Neuron1Spikes"));
2✔
704
        
705
        // Test Count operation
706
        builder.addColumn<int>("Neuron2_Count", 
5✔
707
            std::make_unique<EventInIntervalComputer<int>>(neuron2_source, 
3✔
708
                EventOperation::Count, "Neuron2Spikes"));
2✔
709
        
710
        // Test Gather operation
711
        builder.addColumn<std::vector<float>>("Neuron3_Times", 
5✔
712
            std::make_unique<EventInIntervalComputer<std::vector<float>>>(neuron3_source, 
3✔
713
                EventOperation::Gather, "Neuron3Spikes"));
2✔
714
        
715
        // Build the table
716
        TableView table = builder.build();
1✔
717
        
718
        // Verify table structure
719
        REQUIRE(table.getRowCount() == 4);
1✔
720
        REQUIRE(table.getColumnCount() == 3);
1✔
721
        REQUIRE(table.hasColumn("Neuron1_Present"));
3✔
722
        REQUIRE(table.hasColumn("Neuron2_Count"));
3✔
723
        REQUIRE(table.hasColumn("Neuron3_Times"));
3✔
724
        
725
        // Get the column data
726
        auto neuron1_present = table.getColumnValues<bool>("Neuron1_Present");
3✔
727
        auto neuron2_counts = table.getColumnValues<int>("Neuron2_Count");
3✔
728
        auto neuron3_times = table.getColumnValues<std::vector<float>>("Neuron3_Times");
3✔
729
        
730
        REQUIRE(neuron1_present.size() == 4);
1✔
731
        REQUIRE(neuron2_counts.size() == 4);
1✔
732
        REQUIRE(neuron3_times.size() == 4);
1✔
733
        
734
        // Verify the spike analysis results
735
        // Expected spikes based on our test data (indices → absolute times):
736
        // Behavior 1 (10-25): Neuron1 (12, 14, 22), Neuron2 (10, 12, 16, 18), Neuron3 (12, 20)
737
        // Behavior 2 (30-40): Neuron1 (32), Neuron2 (30, 32, 36), Neuron3 (36)
738
        // Behavior 3 (50-70): Neuron1 (52, 54, 68), Neuron2 (50, 52, 56, 58, 66, 68), Neuron3 (52, 60, 68)
739
        // Behavior 4 (80-95): Neuron1 (82, 90), Neuron2 (80, 82, 84, 90, 92), Neuron3 (84, 92)
740
        
741
        // Note: Actual results depend on the cross-timeframe conversion implementation
742
        // These tests verify the computer executes without errors and produces reasonable results
743
        for (size_t i = 0; i < 4; ++i) {
5✔
744
            std::cout << "Behavior period " << i << ": Neuron1_Present=" << neuron1_present[i] 
12✔
745
                      << ", Neuron2_Count=" << neuron2_counts[i] 
4✔
746
                      << ", Neuron3_Times_size=" << neuron3_times[i].size() << std::endl;
8✔
747
            
748
            // Neuron1 should be present in all behavior periods (now that spikes are aligned with timeframe)
749
            REQUIRE(neuron1_present[i] == true);
4✔
750
            
751
            // Neuron2 counts should be non-negative and reasonable
752
            REQUIRE(neuron2_counts[i] >= 0);
4✔
753
            REQUIRE(neuron2_counts[i] <= 10); // Reasonable upper bound
4✔
754
            
755
            // Neuron3 times should be reasonable
756
            REQUIRE(neuron3_times[i].size() >= 0);
4✔
757
            REQUIRE(neuron3_times[i].size() <= 5); // Reasonable upper bound
4✔
758
            
759
            // All spike times should be sorted
760
            if (neuron3_times[i].size() > 1) {
4✔
761
                for (size_t j = 1; j < neuron3_times[i].size(); ++j) {
7✔
762
                    REQUIRE(neuron3_times[i][j] >= neuron3_times[i][j-1]);
4✔
763
                }
764
            }
765
        }
766
    }
3✔
767
    
768
    SECTION("Test cross-timeframe event analysis") {
2✔
769
        auto& dm = getDataManager();
1✔
770
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
771
        
772
        // Get sources from different timeframes
773
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");  // behavior_time frame
3✔
774
        auto neuron_source = dme->getEventSource("Neuron1Spikes");        // spike_time frame
3✔
775
        
776
        REQUIRE(behavior_source != nullptr);
1✔
777
        REQUIRE(neuron_source != nullptr);
1✔
778
        
779
        // Verify they have different timeframes
780
        auto behavior_tf = behavior_source->getTimeFrame();
1✔
781
        auto spike_tf = neuron_source->getTimeFrame();
1✔
782
        REQUIRE(behavior_tf != spike_tf);
1✔
783
        REQUIRE(behavior_tf->getTotalFrameCount() == 101);  // behavior_time: 0-100
1✔
784
        REQUIRE(spike_tf->getTotalFrameCount() == 51);      // spike_time: 0,2,4,...,100
1✔
785
        
786
        // Create a simple test with one behavior interval
787
        std::vector<TimeFrameInterval> test_intervals = {
1✔
788
            TimeFrameInterval(TimeFrameIndex(10), TimeFrameIndex(25))  // Behavior period 1
789
        };
3✔
790
        
791
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_tf);
1✔
792
        
793
        TableViewBuilder builder(dme);
1✔
794
        builder.setRowSelector(std::move(row_selector));
1✔
795
        
796
        // Add event analysis columns
797
        builder.addColumn<bool>("Spike_Present", 
5✔
798
            std::make_unique<EventInIntervalComputer<bool>>(neuron_source, 
3✔
799
                EventOperation::Presence, "Neuron1Spikes"));
2✔
800
        
801
        builder.addColumn<int>("Spike_Count", 
5✔
802
            std::make_unique<EventInIntervalComputer<int>>(neuron_source, 
3✔
803
                EventOperation::Count, "Neuron1Spikes"));
2✔
804
        
805
        builder.addColumn<std::vector<float>>("Spike_Times", 
5✔
806
            std::make_unique<EventInIntervalComputer<std::vector<float>>>(neuron_source, 
3✔
807
                EventOperation::Gather, "Neuron1Spikes"));
2✔
808
        
809
        // Build and verify the table
810
        TableView table = builder.build();
1✔
811
        
812
        REQUIRE(table.getRowCount() == 1);
1✔
813
        REQUIRE(table.getColumnCount() == 3);
1✔
814
        
815
        auto spike_present = table.getColumnValues<bool>("Spike_Present");
3✔
816
        auto spike_counts = table.getColumnValues<int>("Spike_Count");
3✔
817
        auto spike_times = table.getColumnValues<std::vector<float>>("Spike_Times");
3✔
818
        
819
        REQUIRE(spike_present.size() == 1);
1✔
820
        REQUIRE(spike_counts.size() == 1);
1✔
821
        REQUIRE(spike_times.size() == 1);
1✔
822
        
823
        // Verify results are reasonable (exact values depend on timeframe conversion)
824
        REQUIRE(spike_present[0] == true);  // Should have spikes in this period
1✔
825
        REQUIRE(spike_counts[0] >= 1);      // Should have at least 1 spike
1✔
826
        REQUIRE(spike_counts[0] <= 10);     // Should have reasonable number of spikes
1✔
827
        REQUIRE(spike_times[0].size() == static_cast<size_t>(spike_counts[0])); // Count should match gathered times
1✔
828
        
829
        std::cout << "Cross-timeframe test - Spike Count: " << spike_counts[0] 
1✔
830
                  << ", Times gathered: " << spike_times[0].size() << std::endl;
1✔
831
    }
3✔
832
}
2✔
833

834
TEST_CASE_METHOD(EventTableRegistryTestFixture, "DM - TV - EventInIntervalComputer via ComputerRegistry", "[EventInIntervalComputer][Registry]") {
3✔
835
    
836
    SECTION("Verify EventInIntervalComputer is registered in ComputerRegistry") {
3✔
837
        auto& registry = getTableRegistry().getComputerRegistry();
1✔
838
        
839
        // Check that all event interval computers are registered
840
        auto presence_info = registry.findComputerInfo("Event Presence");
3✔
841
        auto count_info = registry.findComputerInfo("Event Count");
3✔
842
        auto gather_info = registry.findComputerInfo("Event Gather");
3✔
843
        
844
        REQUIRE(presence_info != nullptr);
1✔
845
        REQUIRE(count_info != nullptr);
1✔
846
        REQUIRE(gather_info != nullptr);
1✔
847
        
848
        // Verify computer info details for Presence
849
        REQUIRE(presence_info->name == "Event Presence");
1✔
850
        REQUIRE(presence_info->outputType == typeid(bool));
1✔
851
        REQUIRE(presence_info->outputTypeName == "bool");
1✔
852
        REQUIRE(presence_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
853
        REQUIRE(presence_info->requiredSourceType == typeid(std::shared_ptr<IEventSource>));
1✔
854
        
855
        // Verify computer info details for Count
856
        REQUIRE(count_info->name == "Event Count");
1✔
857
        REQUIRE(count_info->outputType == typeid(int));
1✔
858
        REQUIRE(count_info->outputTypeName == "int");
1✔
859
        REQUIRE(count_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
860
        REQUIRE(count_info->requiredSourceType == typeid(std::shared_ptr<IEventSource>));
1✔
861
        
862
        // Verify computer info details for Gather
863
        REQUIRE(gather_info->name == "Event Gather");
1✔
864
        REQUIRE(gather_info->outputType == typeid(std::vector<float>));
1✔
865
        REQUIRE(gather_info->outputTypeName == "std::vector<float>");
1✔
866
        REQUIRE(gather_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
867
        REQUIRE(gather_info->requiredSourceType == typeid(std::shared_ptr<IEventSource>));
1✔
868
    }
3✔
869
    
870
    SECTION("Create EventInIntervalComputer via ComputerRegistry") {
3✔
871
        auto& dm = getDataManager();
1✔
872
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
873
        auto& registry = getTableRegistry().getComputerRegistry();
1✔
874
        
875
        // Get neuron source for testing
876
        auto neuron_source = dme->getEventSource("Neuron1Spikes");
3✔
877
        REQUIRE(neuron_source != nullptr);
1✔
878
        
879
        // Create computers via registry
880
        std::map<std::string, std::string> empty_params;
1✔
881
        
882
        auto presence_computer = registry.createTypedComputer<bool>(
1✔
883
            "Event Presence", neuron_source, empty_params);
3✔
884
        auto count_computer = registry.createTypedComputer<int>(
1✔
885
            "Event Count", neuron_source, empty_params);
3✔
886
        
887
        REQUIRE(presence_computer != nullptr);
1✔
888
        REQUIRE(count_computer != nullptr);
1✔
889
        
890
        // Test gather computer with parameters
891
        std::map<std::string, std::string> gather_params;
1✔
892
        gather_params["mode"] = "absolute";
3✔
893
        auto gather_computer = registry.createTypedComputer<std::vector<float>>(
1✔
894
            "Event Gather", neuron_source, gather_params);
3✔
895
        REQUIRE(gather_computer != nullptr);
1✔
896
        
897
        // Test gather computer with centered parameter
898
        std::map<std::string, std::string> center_params;
1✔
899
        center_params["mode"] = "centered";
3✔
900
        auto center_computer = registry.createTypedComputer<std::vector<float>>(
1✔
901
            "Event Gather", neuron_source, center_params);
3✔
902
        REQUIRE(center_computer != nullptr);
1✔
903
        
904
        // Test that the created computers work correctly
905
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
906
        
907
        // Create a simple test interval
908
        std::vector<TimeFrameInterval> test_intervals = {
1✔
909
            TimeFrameInterval(TimeFrameIndex(50), TimeFrameIndex(70))  // Behavior period 3
910
        };
3✔
911
        
912
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_time_frame);
1✔
913
        
914
        TableViewBuilder builder(dme);
1✔
915
        builder.setRowSelector(std::move(row_selector));
1✔
916
        
917
        // Use the registry-created computers
918
        builder.addColumn("RegistryPresence", std::move(presence_computer));
3✔
919
        builder.addColumn("RegistryCount", std::move(count_computer));
3✔
920
        builder.addColumn("RegistryGather", std::move(gather_computer));
3✔
921
        builder.addColumn("RegistryCenter", std::move(center_computer));
3✔
922
        
923
        // Build and verify the table
924
        TableView table = builder.build();
1✔
925
        
926
        REQUIRE(table.getRowCount() == 1);
1✔
927
        REQUIRE(table.getColumnCount() == 4);
1✔
928
        REQUIRE(table.hasColumn("RegistryPresence"));
3✔
929
        REQUIRE(table.hasColumn("RegistryCount"));
3✔
930
        REQUIRE(table.hasColumn("RegistryGather"));
3✔
931
        REQUIRE(table.hasColumn("RegistryCenter"));
3✔
932
        
933
        auto presence = table.getColumnValues<bool>("RegistryPresence");
3✔
934
        auto counts = table.getColumnValues<int>("RegistryCount");
3✔
935
        auto gather_times = table.getColumnValues<std::vector<float>>("RegistryGather");
3✔
936
        auto center_times = table.getColumnValues<std::vector<float>>("RegistryCenter");
3✔
937
        
938
        REQUIRE(presence.size() == 1);
1✔
939
        REQUIRE(counts.size() == 1);
1✔
940
        REQUIRE(gather_times.size() == 1);
1✔
941
        REQUIRE(center_times.size() == 1);
1✔
942
        
943
        // Verify reasonable results
944
        REQUIRE(presence[0] == true);  // Should have spikes in this period
1✔
945
        REQUIRE(counts[0] >= 1);       // Should have at least 1 spike
1✔
946
        REQUIRE(counts[0] <= 10);      // Should have reasonable number of spikes
1✔
947
        REQUIRE(gather_times[0].size() == static_cast<size_t>(counts[0]));
1✔
948
        REQUIRE(center_times[0].size() == static_cast<size_t>(counts[0]));
1✔
949
        
950
        std::cout << "Registry test - Presence: " << presence[0] 
3✔
951
                  << ", Count: " << counts[0] 
1✔
952
                  << ", Gather size: " << gather_times[0].size()
1✔
953
                  << ", Center size: " << center_times[0].size() << std::endl;
2✔
954
    }
4✔
955
    
956
    SECTION("Compare registry-created vs direct-created computers") {
3✔
957
        auto& dm = getDataManager();
1✔
958
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
959
        auto& registry = getTableRegistry().getComputerRegistry();
1✔
960
        
961
        auto neuron_source = dme->getEventSource("Neuron2Spikes");
3✔
962
        REQUIRE(neuron_source != nullptr);
1✔
963
        
964
        // Create computer via registry
965
        std::map<std::string, std::string> empty_params;
1✔
966
        auto registry_computer = registry.createTypedComputer<int>(
1✔
967
            "Event Count", neuron_source, empty_params);
3✔
968
        
969
        // Create computer directly
970
        auto direct_computer = std::make_unique<EventInIntervalComputer<int>>(
1✔
971
            neuron_source, EventOperation::Count, "Neuron2Spikes");
1✔
972
        
973
        REQUIRE(registry_computer != nullptr);
1✔
974
        REQUIRE(direct_computer != nullptr);
1✔
975
        
976
        // Test both computers with the same data
977
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
978
        std::vector<TimeFrameInterval> test_intervals = {
1✔
979
            TimeFrameInterval(TimeFrameIndex(80), TimeFrameIndex(95))
980
        };
3✔
981
        
982
        ExecutionPlan plan(test_intervals, behavior_time_frame);
1✔
983
        
984
        auto [registry_result, registry_entity_ids] = registry_computer->compute(plan);
1✔
985
        auto [direct_result, direct_entity_ids] = direct_computer->compute(plan);
1✔
986
        
987
        REQUIRE(registry_result.size() == 1);
1✔
988
        REQUIRE(direct_result.size() == 1);
1✔
989
        
990
        // Results should be identical
991
        REQUIRE(registry_result[0] == direct_result[0]);
1✔
992
        
993
        std::cout << "Comparison test - Registry result: " << registry_result[0] 
1✔
994
                  << ", Direct result: " << direct_result[0] << std::endl;
1✔
995
    }
4✔
996
}
3✔
997

998
TEST_CASE_METHOD(EventTableRegistryTestFixture, "DM - TV - EventInIntervalComputer via JSON TablePipeline", "[EventInIntervalComputer][JSON][Pipeline]") {
2✔
999
    
1000
    SECTION("Test Event analysis operations via JSON pipeline") {
2✔
1001
        // JSON configuration for testing EventInIntervalComputer through TablePipeline
1002
        char const * json_config = R"({
1✔
1003
            "metadata": {
1004
                "name": "Event Interval Analysis Test",
1005
                "description": "Test JSON execution of EventInIntervalComputer",
1006
                "version": "1.0"
1007
            },
1008
            "tables": [
1009
                {
1010
                    "table_id": "event_interval_test",
1011
                    "name": "Event Interval Analysis Table",
1012
                    "description": "Test table using EventInIntervalComputer",
1013
                    "row_selector": {
1014
                        "type": "interval",
1015
                        "source": "BehaviorPeriods"
1016
                    },
1017
                    "columns": [
1018
                        {
1019
                            "name": "SpikePresent",
1020
                            "description": "Presence of spikes in each behavior period",
1021
                            "data_source": "Neuron1Spikes",
1022
                            "computer": "Event Presence"
1023
                        },
1024
                        {
1025
                            "name": "SpikeCount",
1026
                            "description": "Count of spikes in each behavior period",
1027
                            "data_source": "Neuron2Spikes",
1028
                            "computer": "Event Count"
1029
                        },
1030
                        {
1031
                            "name": "SpikeTimes",
1032
                            "description": "Spike times within each behavior period",
1033
                            "data_source": "Neuron3Spikes",
1034
                            "computer": "Event Gather",
1035
                            "parameters": {
1036
                                "mode": "absolute"
1037
                            }
1038
                        }
1039
                    ]
1040
                }
1041
            ]
1042
        })";
1043

1044
        auto& pipeline = getTablePipeline();
1✔
1045

1046
        // Parse the JSON configuration
1047
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1048

1049
        // Load configuration into pipeline
1050
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1051
        REQUIRE(load_success);
1✔
1052

1053
        // Verify configuration was loaded correctly
1054
        auto table_configs = pipeline.getTableConfigurations();
1✔
1055
        REQUIRE(table_configs.size() == 1);
1✔
1056

1057
        auto const& config = table_configs[0];
1✔
1058
        REQUIRE(config.table_id == "event_interval_test");
1✔
1059
        REQUIRE(config.name == "Event Interval Analysis Table");
1✔
1060
        REQUIRE(config.columns.size() == 3);
1✔
1061

1062
        // Verify column configurations
1063
        auto const& column1 = config.columns[0];
1✔
1064
        REQUIRE(column1["name"] == "SpikePresent");
1✔
1065
        REQUIRE(column1["computer"] == "Event Presence");
1✔
1066
        REQUIRE(column1["data_source"] == "Neuron1Spikes");
1✔
1067

1068
        auto const& column2 = config.columns[1];
1✔
1069
        REQUIRE(column2["name"] == "SpikeCount");
1✔
1070
        REQUIRE(column2["computer"] == "Event Count");
1✔
1071
        REQUIRE(column2["data_source"] == "Neuron2Spikes");
1✔
1072

1073
        auto const& column3 = config.columns[2];
1✔
1074
        REQUIRE(column3["name"] == "SpikeTimes");
1✔
1075
        REQUIRE(column3["computer"] == "Event Gather");
1✔
1076
        REQUIRE(column3["data_source"] == "Neuron3Spikes");
1✔
1077
        REQUIRE(column3["parameters"]["mode"] == "absolute");
1✔
1078

1079
        // Verify row selector configuration
1080
        REQUIRE(config.row_selector["type"] == "interval");
1✔
1081
        REQUIRE(config.row_selector["source"] == "BehaviorPeriods");
1✔
1082

1083
        std::cout << "JSON pipeline configuration loaded and parsed successfully" << std::endl;
1✔
1084

1085
        // Execute the pipeline
1086
        auto pipeline_result = pipeline.execute([](int table_index, std::string const& table_name, int table_progress, int overall_progress) {
2✔
1087
            std::cout << "Building table " << table_index << " (" << table_name << "): "
5✔
1088
                      << table_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
5✔
1089
        });
2✔
1090

1091
        if (pipeline_result.success) {
1✔
1092
            std::cout << "Pipeline executed successfully!" << std::endl;
1✔
1093
            std::cout << "Tables completed: " << pipeline_result.tables_completed << "/" << pipeline_result.total_tables << std::endl;
1✔
1094
            std::cout << "Execution time: " << pipeline_result.total_execution_time_ms << " ms" << std::endl;
1✔
1095

1096
            // Verify the built table exists
1097
            auto& registry = getTableRegistry();
1✔
1098
            REQUIRE(registry.hasTable("event_interval_test"));
3✔
1099

1100
            // Get the built table and verify its structure
1101
            auto built_table = registry.getBuiltTable("event_interval_test");
3✔
1102
            REQUIRE(built_table != nullptr);
1✔
1103

1104
            // Check that the table has the expected columns
1105
            auto column_names = built_table->getColumnNames();
1✔
1106
            std::cout << "Built table has " << column_names.size() << " columns" << std::endl;
1✔
1107
            for (auto const& name : column_names) {
4✔
1108
                std::cout << "  Column: " << name << std::endl;
3✔
1109
            }
1110

1111
            REQUIRE(column_names.size() == 3);
1✔
1112
            REQUIRE(built_table->hasColumn("SpikePresent"));
3✔
1113
            REQUIRE(built_table->hasColumn("SpikeCount"));
3✔
1114
            REQUIRE(built_table->hasColumn("SpikeTimes"));
3✔
1115

1116
            // Verify table has 4 rows (one for each behavior period)
1117
            REQUIRE(built_table->getRowCount() == 4);
1✔
1118

1119
            // Get and verify the computed values
1120
            auto spike_present = built_table->getColumnValues<bool>("SpikePresent");
3✔
1121
            auto spike_counts = built_table->getColumnValues<int>("SpikeCount");
3✔
1122
            auto spike_times = built_table->getColumnValues<std::vector<float>>("SpikeTimes");
3✔
1123

1124
            REQUIRE(spike_present.size() == 4);
1✔
1125
            REQUIRE(spike_counts.size() == 4);
1✔
1126
            REQUIRE(spike_times.size() == 4);
1✔
1127

1128
            for (size_t i = 0; i < 4; ++i) {
5✔
1129
                // Should have spikes in all behavior periods
1130
                REQUIRE(spike_present[i] == true);
4✔
1131
                REQUIRE(spike_counts[i] >= 1);      // Should have at least 1 spike per period
4✔
1132
                REQUIRE(spike_counts[i] <= 10);     // Should have reasonable number of spikes
4✔
1133
                REQUIRE(spike_times[i].size() >= 1); // Should have at least 1 spike time
4✔
1134
                REQUIRE(spike_times[i].size() <= 10); // Should have reasonable number of spike times
4✔
1135
                
1136
                std::cout << "Row " << i << ": Present=" << spike_present[i] 
12✔
1137
                          << ", Count=" << spike_counts[i] 
4✔
1138
                          << ", Times gathered=" << spike_times[i].size() << std::endl;
8✔
1139
            }
1140

1141
        } else {
1✔
UNCOV
1142
            std::cout << "Pipeline execution failed: " << pipeline_result.error_message << std::endl;
×
UNCOV
1143
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1144
        }
1145
    }
3✔
1146
    
1147
    SECTION("Test Event Gather with centered mode via JSON") {
2✔
1148
        char const * json_config = R"({
1✔
1149
            "metadata": {
1150
                "name": "Event Gather Centered Test",
1151
                "description": "Test JSON execution of EventInIntervalComputer with centered gathering"
1152
            },
1153
            "tables": [
1154
                {
1155
                    "table_id": "event_gather_centered_test",
1156
                    "name": "Event Gather Centered Test Table",
1157
                    "description": "Test table using EventInIntervalComputer centered gathering",
1158
                    "row_selector": {
1159
                        "type": "interval",
1160
                        "source": "BehaviorPeriods"
1161
                    },
1162
                    "columns": [
1163
                        {
1164
                            "name": "SpikeTimes_Absolute",
1165
                            "description": "Absolute spike times within each behavior period",
1166
                            "data_source": "Neuron1Spikes",
1167
                            "computer": "Event Gather",
1168
                            "parameters": {
1169
                                "mode": "absolute"
1170
                            }
1171
                        },
1172
                        {
1173
                            "name": "SpikeTimes_Centered",
1174
                            "description": "Spike times relative to interval center",
1175
                            "data_source": "Neuron1Spikes",
1176
                            "computer": "Event Gather",
1177
                            "parameters": {
1178
                                "mode": "centered"
1179
                            }
1180
                        }
1181
                    ]
1182
                }
1183
            ]
1184
        })";
1185

1186
        auto& pipeline = getTablePipeline();
1✔
1187
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1188
        
1189
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1190
        REQUIRE(load_success);
1✔
1191

1192
        auto table_configs = pipeline.getTableConfigurations();
1✔
1193
        REQUIRE(table_configs.size() == 1);
1✔
1194
        
1195
        auto const& config = table_configs[0];
1✔
1196
        REQUIRE(config.columns.size() == 2);
1✔
1197
        REQUIRE(config.columns[0]["parameters"]["mode"] == "absolute");
1✔
1198
        REQUIRE(config.columns[1]["parameters"]["mode"] == "centered");
1✔
1199

1200
        std::cout << "Centered gathering JSON configuration parsed successfully" << std::endl;
1✔
1201
        
1202
        // Execute the pipeline
1203
        auto pipeline_result = pipeline.execute();
1✔
1204
        
1205
        if (pipeline_result.success) {
1✔
1206
            std::cout << "✓ Centered gathering pipeline executed successfully!" << std::endl;
1✔
1207
            
1208
            auto& registry = getTableRegistry();
1✔
1209
            auto built_table = registry.getBuiltTable("event_gather_centered_test");
3✔
1210
            REQUIRE(built_table != nullptr);
1✔
1211
            
1212
            REQUIRE(built_table->getRowCount() == 4);
1✔
1213
            REQUIRE(built_table->getColumnCount() == 2);
1✔
1214
            REQUIRE(built_table->hasColumn("SpikeTimes_Absolute"));
3✔
1215
            REQUIRE(built_table->hasColumn("SpikeTimes_Centered"));
3✔
1216
            
1217
            auto absolute_times = built_table->getColumnValues<std::vector<float>>("SpikeTimes_Absolute");
3✔
1218
            auto centered_times = built_table->getColumnValues<std::vector<float>>("SpikeTimes_Centered");
3✔
1219
            
1220
            REQUIRE(absolute_times.size() == 4);
1✔
1221
            REQUIRE(centered_times.size() == 4);
1✔
1222
            
1223
            // Verify that both modes return the same number of spikes per interval
1224
            for (size_t i = 0; i < 4; ++i) {
5✔
1225
                REQUIRE(absolute_times[i].size() == centered_times[i].size());
4✔
1226
                
1227
                std::cout << "Row " << i << ": " << absolute_times[i].size() 
4✔
1228
                          << " spikes (absolute and centered)" << std::endl;
4✔
1229
            }
1230
        } else {
1✔
UNCOV
1231
            FAIL("Centered gathering pipeline execution failed: " + pipeline_result.error_message);
×
1232
        }
1233
    }
3✔
1234
}
2✔
1235

1236
TEST_CASE("DM - TV - EventInIntervalComputer Complex Scenarios", "[EventInIntervalComputer][Complex]") {
4✔
1237
    
1238
    SECTION("Large number of events and intervals") {
4✔
1239
        // Create time frame
1240
        std::vector<int> timeValues;
1✔
1241
        for (int i = 0; i <= 100; ++i) {
102✔
1242
            timeValues.push_back(i);
101✔
1243
        }
1244
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
1245
        
1246
        // Create many events
1247
        std::vector<float> events;
1✔
1248
        for (float i = 0.5f; i < 100.0f; i += 2.0f) {
51✔
1249
            events.push_back(i);
50✔
1250
        }
1251
        
1252
        auto eventSource = std::make_shared<MockEventSource>(
1✔
1253
            "ManyEvents", timeFrame, events);
1✔
1254
        
1255
        // Create many intervals
1256
        std::vector<TimeFrameInterval> intervals;
1✔
1257
        for (int i = 0; i < 50; i += 5) {
11✔
1258
            intervals.emplace_back(TimeFrameIndex(i), TimeFrameIndex(i + 4));
10✔
1259
        }
1260
        
1261
        ExecutionPlan plan(intervals, timeFrame);
1✔
1262
        
1263
        // Test Count operation
1264
        EventInIntervalComputer<int> countComputer(eventSource, 
1✔
1265
                                                  EventOperation::Count,
1266
                                                  "ManyEvents");
3✔
1267
        
1268
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
1269
        
1270
        REQUIRE(countResults.size() == intervals.size());
1✔
1271
        
1272
        // All results should be non-negative
1273
        for (auto result : countResults) {
11✔
1274
            REQUIRE(result >= 0);
10✔
1275
        }
1276
        
1277
        // Test Presence operation
1278
        EventInIntervalComputer<bool> presenceComputer(eventSource, 
1✔
1279
                                                      EventOperation::Presence,
1280
                                                      "ManyEvents");
3✔
1281
        
1282
        auto [presenceResults, presenceEntity_ids] = presenceComputer.compute(plan);
1✔
1283
        
1284
        REQUIRE(presenceResults.size() == intervals.size());
1✔
1285
        
1286
        // At least some intervals should contain events
1287
        bool hasEvents = false;
1✔
1288
        for (auto result : presenceResults) {
1✔
1289
            if (result) {
1✔
1290
                hasEvents = true;
1✔
1291
                break;
1✔
1292
            }
1293
        }
1294
        REQUIRE(hasEvents == true);
1✔
1295
    }
5✔
1296
    
1297
    SECTION("Events at interval boundaries") {
4✔
1298
        // Create time frame
1299
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
3✔
1300
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
1301
        
1302
        // Create events exactly at interval boundaries
1303
        std::vector<float> events = {0.0f, 2.0f, 4.0f, 6.0f, 8.0f, 10.0f};
3✔
1304
        
1305
        auto eventSource = std::make_shared<MockEventSource>(
1✔
1306
            "BoundaryEvents", timeFrame, events);
1✔
1307
        
1308
        // Create intervals that start and end at event times
1309
        std::vector<TimeFrameInterval> intervals = {
1✔
1310
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Events at both boundaries
1311
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4)), // Events at both boundaries
1312
            TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(6)), // Events at both boundaries
1313
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3)), // Event at end boundary
1314
            TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(5))  // No events at boundaries
1315
        };
3✔
1316
        
1317
        ExecutionPlan plan(intervals, timeFrame);
1✔
1318
        
1319
        // Test Count operation
1320
        EventInIntervalComputer<int> countComputer(eventSource, 
1✔
1321
                                                  EventOperation::Count,
1322
                                                  "BoundaryEvents");
3✔
1323
        
1324
        auto [countResults, entity_ids] = countComputer.compute(plan);
1✔
1325
        
1326
        REQUIRE(countResults.size() == 5);
1✔
1327
        
1328
        // Verify boundary event handling
1329
        REQUIRE(countResults[0] >= 1);  // Should include events at 0 and/or 2
1✔
1330
        REQUIRE(countResults[1] >= 1);  // Should include events at 2 and/or 4
1✔
1331
        REQUIRE(countResults[2] >= 1);  // Should include events at 4 and/or 6
1✔
1332
        REQUIRE(countResults[3] >= 1);  // Should include event at 2
1✔
1333
        REQUIRE(countResults[4] >= 0);  // May or may not include events depending on boundary handling
1✔
1334
    }
5✔
1335
    
1336
    SECTION("Different time frames for rows and events") {
4✔
1337
        // Create different time frames with different scales
1338
        // Row time frame: coarser scale (0, 10, 20, 30, 40, 50)
1339
        std::vector<int> rowTimeValues = {0, 10, 20, 30, 40, 50};
3✔
1340
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
1341
        
1342
        // Event time frame: finer scale (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)
1343
        std::vector<int> eventTimeValues = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30};
3✔
1344
        auto eventTimeFrame = std::make_shared<TimeFrame>(eventTimeValues);
1✔
1345
        
1346
        // Create events using the event time frame scale
1347
        // Events at times: 2, 6, 12, 18, 24, 28 (actual time values)
1348
        std::vector<float> events = {2.0f, 6.0f, 12.0f, 18.0f, 24.0f, 28.0f};
3✔
1349
        
1350
        auto eventSource = std::make_shared<MockEventSource>(
1✔
1351
            "DifferentTimeFrameEvents", eventTimeFrame, events);
1✔
1352
        
1353
        // Create intervals using the row time frame scale
1354
        std::vector<TimeFrameInterval> intervals = {
1✔
1355
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)), // Row time 0-10: should contain events at 2, 6
1356
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(2)), // Row time 10-20: should contain events at 12, 18
1357
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)), // Row time 20-30: should contain events at 24, 28
1358
            TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(4)), // Row time 30-40: should contain no events
1359
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2))  // Row time 0-20: should contain events at 2, 6, 12, 18
1360
        };
3✔
1361
        
1362
        ExecutionPlan plan(intervals, rowTimeFrame);
1✔
1363
        
1364
        // Test Count operation
1365
        EventInIntervalComputer<int> countComputer(eventSource, 
1✔
1366
                                                  EventOperation::Count,
1367
                                                  "DifferentTimeFrameEvents");
3✔
1368
        
1369
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
1370
        
1371
        REQUIRE(countResults.size() == 5);
1✔
1372
        REQUIRE(countResults[0] == 2);  // Interval 0-10: events at 2, 6
1✔
1373
        REQUIRE(countResults[1] == 2);  // Interval 10-20: events at 12, 18
1✔
1374
        REQUIRE(countResults[2] == 2);  // Interval 20-30: events at 24, 28
1✔
1375
        REQUIRE(countResults[3] == 0);  // Interval 30-40: no events
1✔
1376
        REQUIRE(countResults[4] == 4);  // Interval 0-20: events at 2, 6, 12, 18
1✔
1377
        
1378
        // Test Presence operation
1379
        EventInIntervalComputer<bool> presenceComputer(eventSource, 
1✔
1380
                                                      EventOperation::Presence,
1381
                                                      "DifferentTimeFrameEvents");
3✔
1382
        
1383
        auto [presenceResults, presenceEntity_ids] = presenceComputer.compute(plan);
1✔
1384
        
1385
        REQUIRE(presenceResults.size() == 5);
1✔
1386
        REQUIRE(presenceResults[0] == true);   // Interval 0-10: has events
1✔
1387
        REQUIRE(presenceResults[1] == true);   // Interval 10-20: has events
1✔
1388
        REQUIRE(presenceResults[2] == true);   // Interval 20-30: has events
1✔
1389
        REQUIRE(presenceResults[3] == false);  // Interval 30-40: no events
1✔
1390
        REQUIRE(presenceResults[4] == true);   // Interval 0-20: has events
1✔
1391
        
1392
        // Test Gather operation
1393
        EventInIntervalComputer<std::vector<float>> gatherComputer(eventSource, 
1✔
1394
                                                                  EventOperation::Gather,
1395
                                                                  "DifferentTimeFrameEvents");
3✔
1396
        
1397
        auto [gatherResults, gatherEntity_ids] = gatherComputer.compute(plan);
1✔
1398
        
1399
        REQUIRE(gatherResults.size() == 5);
1✔
1400
        
1401
        // Check first interval (0-10)
1402
        REQUIRE(gatherResults[0].size() == 2);
1✔
1403
        REQUIRE(gatherResults[0][0] == Catch::Approx(2.0f).epsilon(0.001f));
1✔
1404
        REQUIRE(gatherResults[0][1] == Catch::Approx(6.0f).epsilon(0.001f));
1✔
1405
        
1406
        // Check second interval (10-20)
1407
        REQUIRE(gatherResults[1].size() == 2);
1✔
1408
        REQUIRE(gatherResults[1][0] == Catch::Approx(12.0f).epsilon(0.001f));
1✔
1409
        REQUIRE(gatherResults[1][1] == Catch::Approx(18.0f).epsilon(0.001f));
1✔
1410
        
1411
        // Check third interval (20-30)
1412
        REQUIRE(gatherResults[2].size() == 2);
1✔
1413
        REQUIRE(gatherResults[2][0] == Catch::Approx(24.0f).epsilon(0.001f));
1✔
1414
        REQUIRE(gatherResults[2][1] == Catch::Approx(28.0f).epsilon(0.001f));
1✔
1415
        
1416
        // Check fourth interval (30-40) - should be empty
1417
        REQUIRE(gatherResults[3].size() == 0);
1✔
1418
        
1419
        // Check fifth interval (0-20) - should contain first 4 events
1420
        REQUIRE(gatherResults[4].size() == 4);
1✔
1421
        REQUIRE(gatherResults[4][0] == Catch::Approx(2.0f).epsilon(0.001f));
1✔
1422
        REQUIRE(gatherResults[4][1] == Catch::Approx(6.0f).epsilon(0.001f));
1✔
1423
        REQUIRE(gatherResults[4][2] == Catch::Approx(12.0f).epsilon(0.001f));
1✔
1424
        REQUIRE(gatherResults[4][3] == Catch::Approx(18.0f).epsilon(0.001f));
1✔
1425
    }
5✔
1426
    
1427
    SECTION("Non-aligned time frames with fractional events") {
4✔
1428
        // Create row time frame with irregular intervals
1429
        std::vector<int> rowTimeValues = {0, 5, 13, 27, 45};
3✔
1430
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
1431
        
1432
        // Create event time frame with different scale
1433
        std::vector<int> eventTimeValues = {0, 3, 7, 11, 15, 19, 23, 31, 39, 47};
3✔
1434
        auto eventTimeFrame = std::make_shared<TimeFrame>(eventTimeValues);
1✔
1435
        
1436
        // Create events with fractional times that fall between time frame points
1437
        std::vector<float> events = {1.5f, 4.2f, 8.7f, 14.1f, 20.8f, 25.3f, 35.6f, 42.9f};
3✔
1438
        
1439
        auto eventSource = std::make_shared<MockEventSource>(
1✔
1440
            "NonAlignedEvents", eventTimeFrame, events);
1✔
1441
        
1442
        // Create intervals using the row time frame
1443
        std::vector<TimeFrameInterval> intervals = {
1✔
1444
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)), // Row time 0-5: should contain events at 1.5, 4.2
1445
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(2)), // Row time 5-13: should contain events at 8.7
1446
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)), // Row time 13-27: should contain events at 14.1, 20.8, 25.3
1447
            TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(4))  // Row time 27-45: should contain events at 35.6, 42.9
1448
        };
3✔
1449
        
1450
        ExecutionPlan plan(intervals, rowTimeFrame);
1✔
1451
        
1452
        // Test Count operation
1453
        EventInIntervalComputer<int> countComputer(eventSource, 
1✔
1454
                                                  EventOperation::Count,
1455
                                                  "NonAlignedEvents");
3✔
1456
        
1457
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
1458
        
1459
        REQUIRE(countResults.size() == 4);
1✔
1460
        REQUIRE(countResults[0] == 2);  // Interval 0-5: events at 1.5, 4.2
1✔
1461
        REQUIRE(countResults[1] == 1);  // Interval 5-13: event at 8.7
1✔
1462
        REQUIRE(countResults[2] == 3);  // Interval 13-27: events at 14.1, 20.8, 25.3
1✔
1463
        REQUIRE(countResults[3] == 2);  // Interval 27-45: events at 35.6, 42.9
1✔
1464
        
1465
        // Test Presence operation
1466
        EventInIntervalComputer<bool> presenceComputer(eventSource, 
1✔
1467
                                                      EventOperation::Presence,
1468
                                                      "NonAlignedEvents");
3✔
1469
        
1470
        auto [presenceResults, presenceEntity_ids] = presenceComputer.compute(plan);
1✔
1471
        
1472
        REQUIRE(presenceResults.size() == 4);
1✔
1473
        REQUIRE(presenceResults[0] == true);   // Interval 0-5: has events
1✔
1474
        REQUIRE(presenceResults[1] == true);   // Interval 5-13: has events
1✔
1475
        REQUIRE(presenceResults[2] == true);   // Interval 13-27: has events
1✔
1476
        REQUIRE(presenceResults[3] == true);   // Interval 27-45: has events
1✔
1477
    }
5✔
1478
}
4✔
1479

1480
TEST_CASE_METHOD(EventTableRegistryTestFixture, "DM - TV - EventInIntervalComputer EntityID Round Trip", "[EventInIntervalComputer][EntityID][TableView]") {
4✔
1481
    
1482
    SECTION("Test Complex EntityID structure with EntityID verification") {
4✔
1483
        auto& dm = getDataManager();
1✔
1484
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1485
        
1486
        // Get the event sources from the DataManager
1487
        auto neuron1_source = dme->getEventSource("Neuron1Spikes");
3✔
1488
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1489
        
1490
        REQUIRE(neuron1_source != nullptr);
1✔
1491
        REQUIRE(behavior_source != nullptr);
1✔
1492
        
1493
        // Get the behavior intervals to use as row selector
1494
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1495
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1496
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1497
        
1498
        REQUIRE(behavior_intervals.size() == 4); // 4 behavior periods
1✔
1499
        
1500
        // Convert to TimeFrameIntervals for row selector
1501
        std::vector<TimeFrameInterval> row_intervals;
1✔
1502
        for (const auto& interval : behavior_intervals) {
5✔
1503
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1504
        }
1505
        
1506
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1507
        
1508
        // Create EventInIntervalComputer for Gather operation (Complex EntityID structure)
1509
        auto gather_computer = std::make_unique<EventInIntervalComputer<std::vector<float>>>(
1✔
1510
            neuron1_source, 
1511
            EventOperation::Gather, 
1✔
1512
            "Neuron1Spikes"
1513
        );
1✔
1514
        
1515
        // Verify EntityID structure is Complex for Gather operations
1516
        REQUIRE(gather_computer->getEntityIdStructure() == EntityIdStructure::Complex);
1✔
1517
        REQUIRE(gather_computer->hasEntityIds());
1✔
1518
        
1519
        // Create TableView builder and add the column
1520
        TableViewBuilder builder(dme);
1✔
1521
        builder.setRowSelector(std::move(row_selector));
1✔
1522
        builder.addColumn<std::vector<float>>("Neuron1_Events", std::move(gather_computer));
3✔
1523
        
1524
        // Build the table
1525
        TableView table = builder.build();
1✔
1526

1527
        table.materializeAll();
1✔
1528
        
1529
        // Verify table structure
1530
        REQUIRE(table.getRowCount() == 4);
1✔
1531
        REQUIRE(table.getColumnCount() == 1);
1✔
1532
        REQUIRE(table.hasColumn("Neuron1_Events"));
3✔
1533
        
1534
        // Test column-level EntityIDs using variant interface
1535
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("Neuron1_Events");
3✔
1536
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1537
        
1538
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1539
        REQUIRE(complex_entity_ids.size() == 4); // One vector of EntityIDs per row
1✔
1540
        
1541
        // Test cell-level EntityID extraction
1542
        for (size_t row = 0; row < 4; ++row) {
5✔
1543
            std::vector<EntityId> cell_entity_ids = table.getCellEntityIds("Neuron1_Events", row);
12✔
1544
            REQUIRE(cell_entity_ids == complex_entity_ids[row]);
4✔
1545
            
1546
            // Each row should have zero or more EntityIDs (depending on events in interval)
1547
            REQUIRE(cell_entity_ids.size() >= 0);
4✔
1548
        }
4✔
1549
        
1550
        std::cout << "✓ Complex EntityID structure test passed for EventInIntervalComputer Gather operations" << std::endl;
1✔
1551
        std::cout << "  - Column EntityIDs: " << complex_entity_ids.size() << " rows" << std::endl;
1✔
1552
        for (size_t i = 0; i < complex_entity_ids.size(); ++i) {
5✔
1553
            std::cout << "    Row " << i << ": " << complex_entity_ids[i].size() << " EntityIDs" << std::endl;
4✔
1554
        }
1555
        
1556
        // Get the event data values for comparison
1557
        auto event_data = table.getColumnValues<std::vector<float>>("Neuron1_Events");
3✔
1558
        REQUIRE(event_data.size() == 4);
1✔
1559
        
1560
        // Verify that the number of EntityIDs matches the number of events for each row
1561
        for (size_t row = 0; row < 4; ++row) {
5✔
1562
            auto row_events = event_data[row];
4✔
1563
            auto row_entity_ids = complex_entity_ids[row];
4✔
1564
            
1565
            // The number of EntityIDs should match the number of events
1566
            REQUIRE(row_entity_ids.size() == row_events.size());
4✔
1567
            
1568
            std::cout << "  Row " << row << ": " << row_events.size() << " events, " 
4✔
1569
                      << row_entity_ids.size() << " EntityIDs" << std::endl;
4✔
1570
        }
4✔
1571
    }
5✔
1572
    
1573
    SECTION("Test EntityID round trip with source data verification") {
4✔
1574
        auto& dm = getDataManager();
1✔
1575
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1576
        
1577
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1578
        auto neuron1_source = dme->getEventSource("Neuron1Spikes");
3✔
1579
        
1580
        REQUIRE(behavior_source != nullptr);
1✔
1581
        REQUIRE(neuron1_source != nullptr);
1✔
1582
        
1583
        // Get original source EntityIDs from the DigitalEventSeries
1584
        auto neuron1_data = dm.getData<DigitalEventSeries>("Neuron1Spikes");
3✔
1585
        REQUIRE(neuron1_data != nullptr);
1✔
1586
        
1587
        auto source_neuron1_entity_ids = neuron1_data->getEntityIds();
1✔
1588
        std::cout << "Source neuron1 data has " << source_neuron1_entity_ids.size() << " EntityIDs" << std::endl;
1✔
1589
        
1590
        // Create row selector from behavior intervals
1591
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1592
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1593
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1594
        
1595
        std::vector<TimeFrameInterval> row_intervals;
1✔
1596
        for (const auto& interval : behavior_intervals) {
5✔
1597
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1598
        }
1599
        
1600
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1601
        
1602
        // Create Gather computer to get all events and their EntityIDs
1603
        auto gather_computer = std::make_unique<EventInIntervalComputer<std::vector<float>>>(
1✔
1604
            neuron1_source, 
1605
            EventOperation::Gather, 
1✔
1606
            "Neuron1Spikes"
1607
        );
1✔
1608
        
1609
        // Create table with the computer
1610
        TableViewBuilder builder(dme);
1✔
1611
        builder.setRowSelector(std::move(row_selector));
1✔
1612
        builder.addColumn<std::vector<float>>("Neuron1_Events", std::move(gather_computer));
3✔
1613
        
1614
        TableView table = builder.build();
1✔
1615

1616
        //table.materializeAll(); // This doesn''t currently work. need to get values
1617

1618
        auto event_data_from_table = table.getColumnValues<std::vector<float>>("Neuron1_Events");
3✔
1619

1620
        // Get all EntityIDs from the column
1621
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("Neuron1_Events");
3✔
1622
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1623
        
1624
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1625
        
1626
        // Collect all unique EntityIDs from the table
1627
        std::set<EntityId> table_entity_ids;
1✔
1628
        for (const auto& row_entity_ids : complex_entity_ids) {
5✔
1629
            for (const auto& entity_id : row_entity_ids) {
13✔
1630
                table_entity_ids.insert(entity_id);
9✔
1631
            }
1632
        }
1633
        
1634
        std::cout << "Table extracted " << table_entity_ids.size() << " unique EntityIDs" << std::endl;
1✔
1635
        
1636
        // Debug: Print source EntityIDs
1637
        INFO("Source EntityIDs from Neuron1Spikes:");
1✔
1638
        for (size_t i = 0; i < source_neuron1_entity_ids.size(); ++i) {
11✔
1639
            INFO("  Source EntityID[" << i << "] = " << source_neuron1_entity_ids[i]);
10✔
1640
        }
10✔
1641
        
1642
        // Debug: Print table EntityIDs
1643
        INFO("Table EntityIDs from EventInIntervalComputer:");
1✔
1644
        for (const auto& entity_id : table_entity_ids) {
10✔
1645
            INFO("  Table EntityID = " << entity_id);
9✔
1646
        }
9✔
1647
        
1648
        // Verify that extracted EntityIDs are a subset of source EntityIDs
1649
        // (Not all source EntityIDs may appear in the table due to interval filtering)
1650
        for (const auto& table_entity_id : table_entity_ids) {
10✔
1651
            bool found = std::find(source_neuron1_entity_ids.begin(), 
9✔
1652
                                 source_neuron1_entity_ids.end(), 
1653
                                 table_entity_id) != source_neuron1_entity_ids.end();
18✔
1654
            REQUIRE(found);
9✔
1655
            INFO("✓ Table EntityID " << table_entity_id << " found in source data");
9✔
1656
        }
9✔
1657
        
1658
        // Verify all EntityIDs are valid (non-zero)
1659
        for (const auto& entity_id : table_entity_ids) {
10✔
1660
            REQUIRE(entity_id != 0);
9✔
1661
        }
1662
        
1663
        // Verify cell-level EntityIDs match column-level EntityIDs
1664
        for (size_t row = 0; row < table.getRowCount(); ++row) {
5✔
1665
            auto cell_entity_ids = table.getCellEntityIds("Neuron1_Events", row);
12✔
1666
            REQUIRE(cell_entity_ids == complex_entity_ids[row]);
4✔
1667
        }
4✔
1668
        
1669
        // Additional verification: check EntityID-to-event mapping
1670
        auto event_data = table.getColumnValues<std::vector<float>>("Neuron1_Events");
3✔
1671
        auto source_events = neuron1_data->getEventSeries();
1✔
1672
        
1673
        std::cout << "Source has " << source_events.size() << " events total" << std::endl;
1✔
1674
        
1675
        for (size_t row = 0; row < table.getRowCount(); ++row) {
5✔
1676
            auto row_events = event_data[row];
4✔
1677
            auto row_entity_ids = complex_entity_ids[row];
4✔
1678
            
1679
            // Verify EntityID count matches event count
1680
            REQUIRE(row_entity_ids.size() == row_events.size());
4✔
1681
            
1682
            // For each event in this row, verify its EntityID corresponds to an event in the source
1683
            for (size_t event_idx = 0; event_idx < row_events.size(); ++event_idx) {
13✔
1684
                auto event_value = row_events[event_idx];
9✔
1685
                auto entity_id = row_entity_ids[event_idx];
9✔
1686
                
1687
                // Find this event in the source data
1688
                bool event_found = false;
9✔
1689
                for (size_t src_idx = 0; src_idx < source_events.size(); ++src_idx) {
54✔
1690
                    if (std::abs(source_events[src_idx] - event_value) < 1e-6f) {
54✔
1691
                        // Found the event, verify the EntityID matches
1692
                        if (src_idx < source_neuron1_entity_ids.size()) {
9✔
1693
                            // Note: This is a simplified check. The actual mapping might be more complex
1694
                            // due to time frame conversions and filtering
1695
                            INFO("Event " << event_value << " at row " << row << " has EntityID " 
9✔
1696
                                 << entity_id << ", source index " << src_idx << " has EntityID " 
1697
                                 << source_neuron1_entity_ids[src_idx]);
1698
                            event_found = true;
9✔
1699
                            break;
9✔
1700
                        }
9✔
1701
                    }
1702
                }
1703
                // We expect to find the event in the source, but the exact EntityID mapping
1704
                // depends on the implementation details of time frame conversion
1705
                INFO("Event " << event_value << " processed with EntityID " << entity_id);
9✔
1706
            }
9✔
1707
        }
4✔
1708
        
1709
        std::cout << "✓ EventInIntervalComputer EntityID round trip test passed" << std::endl;
1✔
1710
        std::cout << "  - All EntityIDs are valid and come from source data" << std::endl;
1✔
1711
        std::cout << "  - Cell-level extraction matches column-level extraction" << std::endl;
1✔
1712
        std::cout << "  - EntityID count matches event count for each interval" << std::endl;
1✔
1713
        std::cout << "  - Extracted EntityIDs verified against original DigitalEventSeries" << std::endl;
1✔
1714
    }
5✔
1715
    
1716
    SECTION("Test Gather_Center operation EntityIDs") {
4✔
1717
        auto& dm = getDataManager();
1✔
1718
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1719
        
1720
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1721
        auto neuron2_source = dme->getEventSource("Neuron2Spikes");
3✔
1722
        
1723
        REQUIRE(behavior_source != nullptr);
1✔
1724
        REQUIRE(neuron2_source != nullptr);
1✔
1725
        
1726
        // Create a simple test with one behavior interval
1727
        std::vector<TimeFrameInterval> test_intervals = {
1✔
1728
            TimeFrameInterval(TimeFrameIndex(30), TimeFrameIndex(40))  // Behavior period 2
1729
        };
3✔
1730
        
1731
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1732
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_time_frame);
1✔
1733
        
1734
        // Create Gather_Center computer
1735
        auto gather_center_computer = std::make_unique<EventInIntervalComputer<std::vector<float>>>(
1✔
1736
            neuron2_source, 
1737
            EventOperation::Gather_Center, 
1✔
1738
            "Neuron2Spikes"
1739
        );
1✔
1740
        
1741
        // Verify EntityID structure is Complex for Gather_Center operations
1742
        REQUIRE(gather_center_computer->getEntityIdStructure() == EntityIdStructure::Complex);
1✔
1743
        REQUIRE(gather_center_computer->hasEntityIds());
1✔
1744
        
1745
        // Create table with the computer
1746
        TableViewBuilder builder(dme);
1✔
1747
        builder.setRowSelector(std::move(row_selector));
1✔
1748
        builder.addColumn<std::vector<float>>("Neuron2_Centered", std::move(gather_center_computer));
3✔
1749
        
1750
        TableView table = builder.build();
1✔
1751

1752
        table.materializeAll();
1✔
1753
        
1754
        // Get EntityIDs and events
1755
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("Neuron2_Centered");
3✔
1756
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1757
        
1758
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1759
        auto event_data = table.getColumnValues<std::vector<float>>("Neuron2_Centered");
3✔
1760
        
1761
        REQUIRE(complex_entity_ids.size() == 1);
1✔
1762
        REQUIRE(event_data.size() == 1);
1✔
1763
        
1764
        // Verify EntityID count matches event count even for centered events
1765
        auto row_events = event_data[0];
1✔
1766
        auto row_entity_ids = complex_entity_ids[0];
1✔
1767
        
1768
        REQUIRE(row_entity_ids.size() == row_events.size());
1✔
1769
        
1770
        // Verify all EntityIDs are valid
1771
        for (const auto& entity_id : row_entity_ids) {
4✔
1772
            REQUIRE(entity_id != 0);
3✔
1773
        }
1774
        
1775
        std::cout << "✓ Gather_Center operation EntityID test passed" << std::endl;
1✔
1776
        std::cout << "  - Events found: " << row_events.size() << std::endl;
1✔
1777
        std::cout << "  - EntityIDs found: " << row_entity_ids.size() << std::endl;
1✔
1778
        std::cout << "  - All EntityIDs are valid" << std::endl;
1✔
1779
    }
5✔
1780
    
1781
    SECTION("Test operations without EntityIDs") {
4✔
1782
        auto& dm = getDataManager();
1✔
1783
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1784
        
1785
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1786
        auto neuron1_source = dme->getEventSource("Neuron1Spikes");
3✔
1787
        
1788
        // Create simple test intervals
1789
        std::vector<TimeFrameInterval> test_intervals = {
1✔
1790
            TimeFrameInterval(TimeFrameIndex(10), TimeFrameIndex(25))
1791
        };
3✔
1792
        
1793
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1794
        auto row_selector1 = std::make_unique<IntervalSelector>(test_intervals, behavior_time_frame);
1✔
1795
        auto row_selector2 = std::make_unique<IntervalSelector>(test_intervals, behavior_time_frame);
1✔
1796
        
1797
        // Create Presence and Count computers (should have no EntityIDs)
1798
        auto presence_computer = std::make_unique<EventInIntervalComputer<bool>>(
1✔
1799
            neuron1_source, EventOperation::Presence, "Neuron1Spikes");
1✔
1800
        auto count_computer = std::make_unique<EventInIntervalComputer<int>>(
1✔
1801
            neuron1_source, EventOperation::Count, "Neuron1Spikes");
1✔
1802
        
1803
        // Verify EntityID structure is Complex for these operations
1804
        REQUIRE(presence_computer->getEntityIdStructure() == EntityIdStructure::Complex);
1✔
1805
        REQUIRE(presence_computer->hasEntityIds());
1✔
1806
        REQUIRE(count_computer->getEntityIdStructure() == EntityIdStructure::Complex);
1✔
1807
        REQUIRE(count_computer->hasEntityIds());
1✔
1808
        
1809
        // Create separate tables
1810
        TableViewBuilder builder1(dme);
1✔
1811
        builder1.setRowSelector(std::move(row_selector1));
1✔
1812
        builder1.addColumn<bool>("Presence", std::move(presence_computer));
3✔
1813
        
1814
        TableViewBuilder builder2(dme);
1✔
1815
        builder2.setRowSelector(std::move(row_selector2));
1✔
1816
        builder2.addColumn<int>("Count", std::move(count_computer));
3✔
1817
        
1818
        TableView presence_table = builder1.build();
1✔
1819
        TableView count_table = builder2.build();
1✔
1820

1821
        auto presence_table_values = presence_table.getColumnValues<bool>("Presence");
3✔
1822
        auto count_table_values = count_table.getColumnValues<int>("Count");
3✔
1823
        
1824
        // Test that EntityIDs return empty/monostate
1825
        ColumnEntityIds presence_entity_ids = presence_table.getColumnEntityIds("Presence");
3✔
1826
        ColumnEntityIds count_entity_ids = count_table.getColumnEntityIds("Count");
3✔
1827
        
1828
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(presence_entity_ids));
1✔
1829
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(count_entity_ids));
1✔
1830
        
1831
        // Test cell-level EntityIDs are empty
1832
        auto presence_cell_ids = presence_table.getCellEntityIds("Presence", 0);
3✔
1833
        auto count_cell_ids = count_table.getCellEntityIds("Count", 0);
3✔
1834
        
1835
        REQUIRE(!presence_cell_ids.empty());
1✔
1836
        REQUIRE(!count_cell_ids.empty());
1✔
1837
        
1838
        std::cout << "✓ Non-EntityID operations test passed" << std::endl;
1✔
1839
        std::cout << "  - Presence and Count operations correctly report no EntityIDs" << std::endl;
1✔
1840
        std::cout << "  - Cell-level EntityID extraction returns empty vectors" << std::endl;
1✔
1841
    }
5✔
1842
}
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc