Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt. Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor. Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов. Обновить справочники устройств, проекты CCS и регрессионные проверки.
81 lines
7.3 KiB
Python
81 lines
7.3 KiB
Python
# Тонкий адаптер реконструкции сигнала и преобразования напряжений в коды ЦАП. Массивы и
|
||
# рабочая память подготавливаются в 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)
|