import numpy as np import time from filter_short_groups import filter_short_groups_c def filter_short_groups_numpy(presence_list, filter_size, device_id, dates_str): """ Original NumPy implementation, kept for comparison purposes. """ # Start timer (optional, for benchmarking) st = time.time() if not presence_list or filter_size <= 1: print(f"NumPy Optimized: Early exit/no processing time: {time.time() - st:.6f}s") return presence_list[:] if isinstance(presence_list, list) else list(presence_list) result = np.array(presence_list, dtype=float) n = len(result) previous_states = set() while True: # Cycle detection current_state_tuple = tuple(result) if current_state_tuple in previous_states: print("NumPy Optimized: Cycle detected, breaking.") break previous_states.add(current_state_tuple) # 1. Calculate the sign of each element (-1, 0, 1) signs = np.sign(result) # 2. Find indices where the sign changes change_indices = np.where(np.diff(signs) != 0)[0] + 1 # 3. Define the boundaries of all consecutive runs boundaries = np.concatenate(([0], change_indices, [n])) # If there's only one segment, no further processing is needed. if len(boundaries) <= 2: break # 4. Vectorized extraction of run properties run_starts = boundaries[:-1] run_ends = boundaries[1:] run_lengths = run_ends - run_starts run_signs = signs[run_starts] # 5. Identify short runs and collect their properties short_runs_to_process = [] for i in range(len(run_starts)): if run_lengths[i] > 0 and run_lengths[i] < filter_size: short_runs_to_process.append({ 'start': run_starts[i], 'end': run_ends[i], 'sign': run_signs[i], 'length': run_lengths[i] }) # 6. Check if any modifiable short runs were found if not short_runs_to_process: break # 7. Sort the short runs: shortest first, then by start index for determinism short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) # 8. Process ONLY the *first* (shortest) identified run in this pass run_to_process = short_runs_to_process[0] start = run_to_process['start'] end = run_to_process['end'] run_sign = run_to_process['sign'] # Determine the replacement value replacement_value = 1.0 if run_sign == 0 else 0.0 # 9. Apply the replacement result[start:end] = replacement_value # End timer and print print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") return result.tolist() def benchmark_comparison(input_data, filter_size, iterations=10): """ Compare performance between NumPy and C implementations """ device_id = "test_device" dates_str = "2025-05-21" # Warm-up runs _ = filter_short_groups_numpy(input_data, filter_size, device_id, dates_str) _ = filter_short_groups_c(input_data, filter_size, device_id, dates_str) # NumPy benchmark numpy_times = [] for _ in range(iterations): start = time.time() numpy_result = filter_short_groups_numpy(input_data, filter_size, device_id, dates_str) numpy_times.append(time.time() - start) # C implementation benchmark c_times = [] for _ in range(iterations): start = time.time() c_result = filter_short_groups_c(input_data, filter_size, device_id, dates_str) c_times.append(time.time() - start) # Check results match results_match = numpy_result == c_result # Print results print(f"\nResults from NumPy and C implementation match: {results_match}") print(f"\nNumPy Implementation:") print(f" Average time: {np.mean(numpy_times):.6f}s") print(f" Min time: {np.min(numpy_times):.6f}s") print(f" Max time: {np.max(numpy_times):.6f}s") print(f"\nC Implementation:") print(f" Average time: {np.mean(c_times):.6f}s") print(f" Min time: {np.min(c_times):.6f}s") print(f" Max time: {np.max(c_times):.6f}s") speedup = np.mean(numpy_times) / np.mean(c_times) print(f"\nSpeedup factor: {speedup:.2f}x") return numpy_result, c_result if __name__ == "__main__": # Example data # Random presence list with mix of 0s and 1s np.random.seed(42) data_small = list(np.random.choice([0.0, 1.0], size=100)) data_medium = list(np.random.choice([0.0, 1.0], size=1000)) data_large = list(np.random.choice([0.0, 1.0], size=10000)) # Example with small data print("\n===== Small dataset (100 elements) =====") numpy_result_small, c_result_small = benchmark_comparison(data_small, filter_size=3) # Example with medium data print("\n===== Medium dataset (1000 elements) =====") numpy_result_medium, c_result_medium = benchmark_comparison(data_medium, filter_size=5) # Example with large data print("\n===== Large dataset (10000 elements) =====") numpy_result_large, c_result_large = benchmark_comparison(data_large, filter_size=10)