"""Восстановление синтетической индукции по ЭДС поисковой катушки.

Среда: встроенный MicroPython FEMM 5.
Вход: число витков N, площадь A [м²], f [Гц], Bpeak [Т], offset [В].
Выход: waveform.csv (время [с], B [Т], напряжение [В]) и results.json.
Условия: один полный период синусоиды, известное B(0)=0 и постоянный offset.
Постоянная составляющая B неподвижной катушкой не восстанавливается.
OUTPUT должен отсутствовать; запись синтетическая, а не измеренная.
"""

import json
import math
import os

OUTPUT = "measurement-coil-run"
# Новый каталог сохраняет предыдущий опыт при повторном запуске.
os.mkdir(OUTPUT)

N = 100
A = 2e-4
f = 50.0
Bpeak = 0.01
offset = 0.002
samples = 4000  # Число интервалов: CSV содержит samples + 1 точку.
dt = 1 / (f * samples)
omega = 2 * math.pi * f
emf_peak = N * A * omega * Bpeak
integration_scale = dt / (N * A)


def voltage_at(k):
    """Синтетическое напряжение, В, в точке k равномерной сетки времени."""
    # Закон Фарадея: e = -N*A*dB/dt; offset добавлен измерительным каналом.
    return -emf_peak * math.cos(2 * math.pi * k / samples) + offset


# Первый проход: среднее на полном периоде по правилу трапеций.
# Две крайние точки имеют половинный вес, поскольку совпадают по фазе.
# Повторная генерация сигнала экономит память: массив из 4001 строки не нужен.
voltage_sum = 0.5 * (voltage_at(0) + voltage_at(samples))
for k in range(1, samples):
    voltage_sum += voltage_at(k)
mean = voltage_sum / samples

# Второй проход: интегрируем исходный и исправленный сигналы одновременно.
# Начальные значения B=0 здесь известны; знак минус следует закону Фарадея.
raw = 0.0
corrected = 0.0
max_error = 0.0
previous_voltage = voltage_at(0)
with open(OUTPUT + "/waveform.csv", "w") as stream:
    stream.write("time_s,synthetic_B_T,voltage_with_offset_V,B_raw_T,B_corrected_T\n")
    for k in range(samples + 1):
        t = k * dt
        b = Bpeak * math.sin(2 * math.pi * k / samples)
        voltage = voltage_at(k)
        if k > 0:
            # Формула трапеций использует только две соседние точки.
            raw -= 0.5 * (previous_voltage + voltage) * integration_scale
            corrected -= (0.5 * (previous_voltage + voltage) - mean) * integration_scale
        max_error = max(max_error, abs(corrected - b))
        # Пишем каждую строку сразу: объём оперативной памяти не растёт с N.
        row = (t, b, voltage, raw, corrected)
        stream.write(",".join(str(value) for value in row) + "\n")
        previous_voltage = voltage

# Дрейф за период известен аналитически: -offset/(f*N*A).
# Сравнение с исходной синусоидой оценивает погрешность квадратуры, а не датчика.
assert abs(raw + offset / f / (N * A)) < 1e-10, "Не совпал аналитический дрейф"
assert max_error < 1e-8, "Слишком грубый шаг интегрирования"
result = {
    "origin": "synthetic_analytical_signal_not_measurement_or_FEMM",
    "emf_rms_V": emf_peak / math.sqrt(2),
    "estimated_offset_V": mean,
    "raw_drift_per_period_T": raw,
    "corrected_max_error_T": max_error,
    "limitations": "Known periodic record and known initial B=0; DC B is not recovered by a stationary coil.",
}
with open(OUTPUT + "/results.json", "w") as stream:
    json.dump(result, stream)
print(json.dumps(result))
