Files
templates/python/set_devices/signal_reconstruction.py
Andrey 41e24629ce Расширить DEIF, загрузку CAN и общую обработку сигналов
Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt.
Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor.
Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов.
Обновить справочники устройств, проекты CCS и регрессионные проверки.
2026-10-24 15:40:51 +03:00

81 lines
7.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Тонкий адаптер реконструкции сигнала и преобразования напряжений в коды ЦАП. Массивы и
# рабочая память подготавливаются в Python, а численный алгоритм и проверки диапазонов
# выполняет общее C-ядро.
"""Thin host adapter for the shared C99 reconstruction/DAC algorithms."""
from __future__ import annotations
import ctypes as C
from dataclasses import dataclass
from functools import lru_cache
from protocan.native import NativeProtocol
from time import perf_counter # Измеряем полное время обработки запроса, включая преобразование входных и выходных буферов.
MAX_OUTPUT_POINTS = 1_000_000
BACKENDS = {"native": "Текущий МНК", "eigen": "Eigen QR"} # Стабильные ключи сохраняются в запросах и рецептах, а подписи показываются в GUI.
METHODS = {"polynomial": "Аппроксимация: полином МНК", "linear": "Интерполяция: линейная",
"pchip": "Интерполяция: PCHIP", "spline": "Восстановление: кубический сплайн"}
@dataclass
class Reconstruction:
points: list
input_count: int
unique_count: int
rmse: float
pieces: tuple = ()
backend: str = "native" # Сохраняем выбранный вариант расчёта для сравнения и подписей результатов.
elapsed_ms: float = 0.0 # Время полного вызова в миллисекундах позволяет оценивать стоимость преобразований вместе с решателем.
@lru_cache(maxsize=1)
def library():
try:
lib = NativeProtocol().lib
pointer = C.POINTER(C.c_double)
lib.set_signal_reconstruct.argtypes = [pointer, pointer, C.c_size_t, C.c_int, C.c_uint,
C.c_size_t, C.c_int, pointer, pointer, pointer, pointer, C.c_size_t]
lib.set_signal_reconstruct.restype = C.c_int
lib.set_signal_dac12.argtypes = [pointer, C.c_size_t, C.c_double, C.POINTER(C.c_uint16)]
lib.set_signal_dac12.restype = C.c_int
return lib
except (AttributeError, OSError, RuntimeError) as error:
raise ValueError("Пересоберите SETProtocol с set_signal.c и set_wavegen.c") from error
def reconstruct(points, method="pchip", output_count=1000, degree=2, *, endpoint=True, with_model=False, backend="native"): # Новый именованный параметр сохраняет все существующие позиционные вызовы.
started = perf_counter() # Начинаем замер до подготовки ctypes-массивов, одинаковой для двух решателей.
if backend not in BACKENDS or (backend == "eigen" and method != "polynomial"): # Eigen предлагается только для полиномиального МНК, а неизвестные ключи отклоняются явно.
raise ValueError("Eigen QR доступен только для полинома МНК; выберите корректный вариант расчёта") # Исключаем скрытое использование другого метода при сравнении.
if method not in METHODS or type(output_count) is not int or not 2 <= output_count <= MAX_OUTPUT_POINTS:
raise ValueError(f"Неизвестный метод или число выходных точек вне 2…{MAX_OUTPUT_POINTS}")
count = len(points)
if not 2 <= count <= 100000 or type(degree) is not int or not 1 <= degree <= 5:
raise ValueError("Нужно 2…100000 исходных точек, степень 1…5")
x, y = (C.c_double * count)(*(p[0] for p in points)), (C.c_double * count)(*(p[1] for p in points))
ox, oy, meta = (C.c_double * output_count)(), (C.c_double * output_count)(), (C.c_double * 3)()
work = (C.c_double * (14 * count + 128))()
core = library() # Существующая библиотека остаётся источником базового алгоритма и каталога установленного модуля.
operation = core.set_signal_reconstruct # По умолчанию сохраняем исходный C99-решатель и его бинарный контракт.
if backend == "eigen": # Загружаем дополнительный компонент только после явного выбора пользователя.
from .signal_eigen import library as eigen_library # Ленивый импорт не требует наличия Eigen при обычной работе и в прошивках.
operation = eigen_library(core._name).set_signal_reconstruct_eigen # Соседняя DLL реализует тот же контракт входов, выходов и work.
code = operation(x, y, count, list(METHODS).index(method), degree, output_count, int(endpoint), ox, oy, meta, work, len(work)) # Оба решателя получают одинаковые данные и настройки сетки.
if code:
raise ValueError({1: "Недостаточно точек для выбранной степени или неверные параметры",
2: "Нужны конечные значения и минимум две различные временные точки",
3: "Неустойчивая аппроксимация: уменьшите степень", 5: "Недостаточно памяти или ошибка решателя Eigen"}.get(code, "Ошибка расчёта")) # Дополнительная настольная ошибка не меняет прежние коды C99.
pieces = ()
if with_model:
from .plot_formula import reconstruction_pieces
if method == 'polynomial' and output_count <= degree:
pieces = reconstruct(points, method, degree + 1, degree, endpoint=endpoint, with_model=True, backend=backend).pieces # Короткий выход требует отдельной сетки для формулы; сохраняем решатель и режим конечной точки.
else:
pieces = reconstruction_pieces(work, count, int(meta[1]), method, degree, oy, endpoint)
result_points = list(zip(ox, oy)) # Включаем формирование выходных Python-точек в отображаемое время обработки.
return Reconstruction(result_points, int(meta[0]), int(meta[1]), meta[2], pieces, backend, (perf_counter() - started) * 1000) # Переводим секунды монотонного таймера в миллисекунды без влияния изменения системных часов.
def dac12(volts, vref=3.3):
data = (C.c_double * len(volts))(*volts)
codes = (C.c_uint16 * len(volts))()
if library().set_signal_dac12(data, len(data), vref, codes):
raise ValueError("Расчётная кривая выходит за диапазон ЦАП 0…Vref; измените точки или метод")
return tuple(codes)