"""Градирование изоляции высоковольтного кабеля.
Среда: встроенный MicroPython FEMM 5; команды FEMM доступны глобально.
Геометрические размеры: миллиметры; поля и интегралы возвращаются в СИ.
Все материалы и проводники имеют русские имена. Каждая модель сохраняется,
закрывается и повторно открывается до нового расчёта, включая кириллицу.
Запуск создаёт новую папку calculated-NN рядом с программой; опубликованные
модели, прежние расчёты и квитанции не перезаписываются. Все необходимые
исходные данные включены. Условия задачи и ограничения описаны в README.md.
"""
import os
import sys
import math
import json

saved = globals().get('__file__', '') or (sys.argv[0] if sys.argv else '')
base = os.path.dirname(os.path.abspath(saved)) if saved else os.getcwd()
index = 1
while os.path.exists(base + '/calculated-%02d' % index):
    index += 1
directory = base + '/calculated-%02d' % index
os.mkdir(directory)
records = []
checks = []
EPS0 = 8.85418781762e-12


def real(value):
    if isinstance(value, (list, tuple)):
        return real(value[0])
    return complex(value).real


def check(name, value, limit):
    checks.append({'name': name, 'value': value, 'limit': limit,
                   'passed': math.isfinite(value) and value <= limit})


def label(x, y, material, mesh, group=1):
    ei_addblocklabel(x, y)
    ei_selectlabel(x, y)
    ei_setblockprop(material, 0, mesh, group)
    ei_clearselected()


def circle(x, radius, conductor, angle):
    ei_addnode(x - radius, 0)
    ei_addnode(x + radius, 0)
    ei_addarc(x - radius, 0, x + radius, 0, 180, angle)
    ei_addarc(x + radius, 0, x - radius, 0, 180, angle)
    ei_selectarcsegment(x, radius)
    ei_selectarcsegment(x, -radius)
    ei_setarcsegmentprop(angle, '<None>', 0, 0, conductor)
    ei_clearselected()


def solve(model):
    ei_saveas(model)
    ei_close()
    opendocument(model)
    ei_analyze(1)
    ei_loadsolution()
    eo_smooth('off')


def electric_result(mesh, model, input_name, output_name, exact_C):
    voltage_in, charge_in = eo_getconductorproperties(input_name)
    voltage_out, charge_out = eo_getconductorproperties(output_name)
    voltage = real(voltage_in) - real(voltage_out)
    charge = real(charge_in)
    eo_groupselectblock(1)
    energy = real(eo_blockintegral(0))
    eo_clearblock()
    capacitance = charge / voltage
    energy_C = 2 * energy / voltage ** 2
    row = {'mesh_mm': mesh, 'model': os.path.basename(model),
           'nodes': eo_numnodes(), 'elements': eo_numelements(),
           'voltage_V': voltage, 'charge_C': charge, 'energy_J': energy,
           'capacitance_F': capacitance, 'energy_capacitance_F': energy_C,
           'analytic_capacitance_F': exact_C,
           'analytic_C_relative_error': abs(capacitance / exact_C - 1),
           'energy_charge_relative_error': abs(energy_C / capacitance - 1)}
    records.append(row)
    check('Энергия и заряд: ' + os.path.basename(model),
          row['energy_charge_relative_error'], 1e-5)
    return row


U = 110000
a = 0.007
c = 0.019
b = 0.036
for kind in ('Однослойная изоляция', 'Градированная изоляция'):
    graded = kind == 'Градированная изоляция'
    eps1, eps2 = (10, 3.68) if graded else (2.3, 2.3)
    denominator = math.log(c / a) / eps1 + math.log(b / c) / eps2
    exact_C = 2 * math.pi * EPS0 / denominator
    emax = max(U / (denominator * eps1 * a),
               U / (denominator * eps2 * c))
    for level, mesh in enumerate((0.6, 0.3, 0.15)):
        newdocument(1)
        ei_probdef('millimeters', 'planar', 1e-10, 1000, 30)
        ei_addmaterial('Внутренний слой изоляции', eps1, eps1, 0)
        ei_addmaterial('Внешний слой изоляции', eps2, eps2, 0)
        ei_addconductorprop('Жила под напряжением', U, 0, 1)
        ei_addconductorprop('Заземлённая оболочка', 0, 0, 1)
        angle = 4 / (2 ** level)
        circle(0, 7, 'Жила под напряжением', angle)
        circle(0, 19, '<None>', angle)
        circle(0, 36, 'Заземлённая оболочка', angle)
        label(0, 0, '<No Mesh>', mesh, 0)
        label(12, 0, 'Внутренний слой изоляции', mesh)
        label(25, 0, 'Внешний слой изоляции', mesh)
        stem = 'graded' if graded else 'single'
        model = directory + '/' + stem + '-%02d.fee' % (level + 1)
        solve(model)
        row = electric_result(mesh, model, 'Жила под напряжением', 'Заземлённая оболочка', exact_C)
        row['insulation'] = kind
        row['analytic_Emax_kV_mm'] = emax / 1e6
        from_charge = max(row['charge_C'] / (2 * math.pi * EPS0 * eps1 * a),
                          row['charge_C'] / (2 * math.pi * EPS0 * eps2 * c))
        row['Emax_from_charge_kV_mm'] = from_charge / 1e6
        for sample_r, eps, field_key in ((0.010, eps1, 'E_10mm_kV_mm'),
                                         (0.025, eps2, 'E_25mm_kV_mm')):
            values = eo_getpointvalues(sample_r * 1000, 0)
            field = math.sqrt(real(values[3]) ** 2 + real(values[4]) ** 2)
            expected = U / (denominator * eps * sample_r)
            row[field_key] = field / 1e6
            check('Радиальное поле ' + kind + ', r=' + str(sample_r),
                  abs(field / expected - 1), 0.04)
        check('Аналитическая ёмкость ' + kind + ', сетка %d' % (level + 1),
              row['analytic_C_relative_error'], 0.003)
        eo_close()
        ei_close()
    check('Сходимость ёмкости ' + kind, abs(records[-1]['capacitance_F'] /
          records[-2]['capacitance_F'] - 1), 0.001)
single, graded = records[2], records[5]
check('Градирование снижает максимум поля', max(0, graded['Emax_from_charge_kV_mm'] /
      single['Emax_from_charge_kV_mm'] - 1), 0)

columns = {
    'mesh_mm': 'Размер сетки, мм', 'model': 'Файл модели',
    'nodes': 'Узлов', 'elements': 'Элементов',
    'voltage_V': 'Разность потенциалов, В', 'charge_C': 'Заряд, Кл',
    'energy_J': 'Энергия, Дж', 'capacitance_F': 'Ёмкость по заряду, Ф',
    'energy_capacitance_F': 'Ёмкость по энергии, Ф',
    'analytic_capacitance_F': 'Аналитическая ёмкость, Ф',
    'analytic_C_relative_error': 'Относительная ошибка ёмкости',
    'energy_charge_relative_error': 'Невязка энергии и заряда',
    'E_7p5mm_V_m': 'Поле при r=7,5 мм, В/м',
    'analytic_E_7p5mm_V_m': 'Аналитическое поле при r=7,5 мм, В/м',
    'E_150mm_V_m': 'Поле при r=150 мм, В/м',
    'analytic_E_150mm_V_m': 'Аналитическое поле при r=150 мм, В/м',
    'outer_radius_mm': 'Радиус внешней границы, мм',
    'insulation': 'Изоляция', 'analytic_Emax_kV_mm': 'Аналитический максимум поля, кВ/мм',
    'Emax_from_charge_kV_mm': 'Максимум поля по заряду, кВ/мм',
    'E_10mm_kV_mm': 'Поле при r=10 мм, кВ/мм',
    'E_25mm_kV_mm': 'Поле при r=25 мм, кВ/мм',
    'conductivity_S_m': 'Эквивалентная проводимость, См/м',
    'I_real_A': 'Активная компонента амплитуды тока, А',
    'I_imag_A': 'Квадратурная компонента амплитуды тока, А',
    'analytic_I_real_A': 'Аналитическая активная компонента, А',
    'analytic_I_imag_A': 'Аналитическая квадратурная компонента, А',
    'losses_W': 'Диэлектрические потери, Вт',
    'analytic_losses_W': 'Аналитические потери, Вт',
    'terminal_power_W': 'Средняя мощность электрода, Вт',
    'apparent_power_VA': 'Полная мощность, ВА',
    'U_RMS_V': 'Действующее напряжение, В', 'U_amplitude_V': 'Амплитуда напряжения, В'
}
result = {'runtime': 'FEMM 5.0; встроенный MicroPython', 'records': records,
          'checks': checks, 'columns': columns,
          'passed': all(item['passed'] for item in checks)}
with open(directory + '/result.json', 'w') as output:
    json.dump(result, output)
print(json.dumps(result))
assert result['passed'], 'Не выполнен численный критерий'
