Added final details for offline processing

Added merge buffer methods and a (very inefficient) nearest neighbor
system.
This commit is contained in:
h3cx committed 2026-09-29 14:14:27 +02:00
1 parent 6b954287a4
commit 0a0f01c321
2 files changed
+75 -4

No files matched your search

+31
View File
@@ -2,6 +2,7 @@
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com> # Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
from bisect import bisect_left from bisect import bisect_left
from collections import defaultdict from collections import defaultdict
import csv import csv
@@ -20,6 +21,10 @@ class ValueBuffer:
with self._lock: with self._lock:
self._samples.append((timestamp, uuid, value)) self._samples.append((timestamp, uuid, value))
def merge(self, buffer: ValueBuffer) -> None:
with self._lock:
self._samples += buffer._samples
def clear(self) -> None: def clear(self) -> None:
with self._lock: with self._lock:
self._samples.clear() self._samples.clear()
@@ -32,6 +37,32 @@ class ValueBuffer:
with self._lock: with self._lock:
return list({signal_id for _, signal_id, _ in self._samples}) return list({signal_id for _, signal_id, _ in self._samples})
def get_nearest_neighbor_no_lock(self, uuid: UUID, timestamp: int) -> float | None:
previous: tuple[int, UUID, float] | None = None
for sample in self._samples:
if sample[1] != uuid:
continue
if sample[0] >= timestamp:
if previous is None:
return sample[2]
d1 = abs(sample[0] - timestamp)
d2 = abs(previous[0] - timestamp)
if d1 < d2:
return sample[2]
else:
return previous[2]
previous = sample
if previous is not None:
return previous[2]
return None
def get_nearest_neighbor(self, uuid: UUID, timestamp: int) -> float | None: def get_nearest_neighbor(self, uuid: UUID, timestamp: int) -> float | None:
previous: tuple[int, UUID, float] | None = None previous: tuple[int, UUID, float] | None = None
+44 -4
View File
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from collections.abc import Callable from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from copy import copy
from datetime import datetime, timezone from datetime import datetime, timezone
from enum import IntEnum from enum import IntEnum
import inspect import inspect
@@ -200,9 +202,12 @@ class DeriveUnit:
def is_running(self) -> bool: def is_running(self) -> bool:
return not self._stopped_event.is_set() return not self._stopped_event.is_set()
def process_offline(self, buffer: ValueBuffer) -> ValueBuffer: def _process_offline_worker(
self, buffer: ValueBuffer, start: int, end: int
) -> ValueBuffer:
samples = buffer._samples.copy()
output_buffer: ValueBuffer = ValueBuffer() output_buffer: ValueBuffer = ValueBuffer()
for sample in buffer._samples: for sample in samples[start:end]:
if sample[1] in [signal.id for signal in self._input_signals]: if sample[1] in [signal.id for signal in self._input_signals]:
input_args: list[ValueDescriptor] = [] input_args: list[ValueDescriptor] = []
for signal in self._input_signals: for signal in self._input_signals:
@@ -214,12 +219,47 @@ class DeriveUnit:
input_args.append( input_args.append(
ValueDescriptor( ValueDescriptor(
signal.id, signal.id,
buffer.get_nearest_neighbor(signal.id, sample[0]), buffer.get_nearest_neighbor_no_lock(
signal.id, sample[0]
),
sample[0], sample[0],
) )
) )
result = self._process_function(*input_args, self._return_signal) result = self._process_function(*input_args, self._return_signal)
output_buffer.append(result.timestamp, self._return_signal.id, result.value) output_buffer.append(
result.timestamp, self._return_signal.id, result.value
)
return output_buffer
def process_offline(self, buffer: ValueBuffer) -> ValueBuffer:
num_samples = len(buffer._samples)
num_chunks = 16
chunk_size = num_samples // num_chunks
output_buffer: ValueBuffer = ValueBuffer()
with ThreadPoolExecutor(max_workers=num_chunks) as executor:
ranges: list[tuple[int, int]] = []
for i in range(num_chunks):
start = i * chunk_size
if i == num_chunks - 1:
end = num_samples
else:
end = (i + 1) * chunk_size
ranges.append((start, end))
futures = [
executor.submit(self._process_offline_worker, buffer, *range)
for range in ranges
]
results = [future.result() for future in futures]
for result in results:
output_buffer.merge(result)
return output_buffer return output_buffer