# Учебная редакция: назначение, единицы и этапы алгоритма пояснены по-русски.
# Исходная программа остаётся самостоятельным свидетельством прежней проверки.
# Развиваем шаг Хойна из II08-2.py: вводим отключение при известном угле.
# Линейная аналитическая таблица; заданная скорость; после отключения свободный ход U=0.
# Динамика механической части и полевая таблица вентильно-индукторной машины здесь не получаются.
import math


def current_from_linkage(linkage, limit=.2):
    """Обратить учебную линейную зависимость Ψ = L·I, L = 0,01 Гн.
    
    Потокосцепление и limit заданы в Вб, возвращаемый ток — в А.
    Допустимая область 0 ≤ Ψ ≤ limit проверяется до использования;
    экстраполяция за границы таблицы запрещена."""
    if linkage < 0 or linkage > limit:
        raise ValueError("Outside magnetic map; no extrapolation")
    return linkage/.01  # L=0,01 Гн > 0; исходная область таблицы 0…20 А.


def integrate(step, end=.01, omega=100., turn_off_angle=.3, limit=.2):
    """Интегрировать dΨ/dt = U − R·I методом Хойна с точным попаданием в коммутацию.
    
    step и end — в секундах, omega — заданная постоянная скорость, рад/с,
    turn_off_angle — механический угол отключения, рад; R = 2 Ом.
    До отключения U = 24 В, после него U = 0 — идеальный свободный ход.
    Механическое уравнение и новая полевая таблица здесь не решаются."""
    if step <= 0 or end <= 0 or omega <= 0 or turn_off_angle <= 0:
        raise ValueError("Positive step, end, imposed speed and angle required")
    # Известный угол при постоянной скорости задаёт точное время отключения.
    event = turn_off_angle/omega
    time, linkage, switches = 0., 0., 0
    for unused in range(100000):
        if time >= end-1e-14:
            return current_from_linkage(linkage, limit), time, switches
        before = time < event-1e-14
        # Укорачиваем шаг до конца расчёта или ближайшей коммутации.
        h = min(step, end-time, event-time if before else end-time)
        u = 24. if before else 0.
        # В уравнении для Ψ правая часть имеет размерность В = Вб/с.
        first = u-2*current_from_linkage(linkage, limit)
        # Предварительный шаг Эйлера даёт вторую оценку правой части для метода Хойна.
        predicted = linkage+h*first
        second = u-2*current_from_linkage(predicted, limit)
        # Усредняем два наклона и снова проверяем допустимость нового состояния.
        linkage += .5*h*(first+second)
        current_from_linkage(linkage, limit)
        time += h
        if before and abs(time-event) < 1e-13:
            switches += 1
    raise ValueError("Time step budget exhausted")


def run_checks():
    """Сопоставить два временных шага с точным решением и проверить границу таблицы."""
    # Точное экспоненциальное решение состоит из разгона тока и свободного спада.
    exact = 12*(1-math.exp(-2*.003/.01))*math.exp(-2*(.01-.003)/.01)
    one, two = integrate(.0001), integrate(.00005)
    assert abs(one[1]-two[1]) < 1e-14 and abs(one[1]-.01) < 1e-14 and one[2] == two[2] == 1
    assert abs(two[0]-exact) < abs(one[0]-exact) and abs(two[0]-exact) < .0001
    assert integrate(.0023)[2] == 1  # Событие находится внутри предложенного шага.
    rejected = False
    try:
        integrate(.0001, limit=.001)
    except ValueError:
        rejected = True
    assert rejected
    return {"exact_A": exact, "dt_0_1ms_A": one[0], "dt_0_05ms_A": two[0], "turn_off_s": .003, "end_s": two[1], "range_failure_rejected": rejected}
