"""Параметры трансформатора, нагрузка и насыщение. Встроенный Python FEMM 5.

Поместите программу и пять исходных FEM в одну папку. Программа создаёт
новую папку Расчет-NN, сохраняет модели, решения и численные результаты.
Токи и напряжения таблицы нагрузки — действующие фазоры. В FEMM передаются
комплексные амплитуды, то есть токи RMS, умноженные на sqrt(2).
"""
import os,sys,json,math
saved=globals().get('__file__','') or (sys.argv[0] if sys.argv else '')
BASE=globals().get('BASE',os.path.dirname(os.path.abspath(saved)) if saved else os.getcwd())
OUTPUT=globals().get('OUTPUT',BASE)
n=1
while os.path.exists(OUTPUT+'/Расчет-%02d'%n):n+=1
OUT=OUTPUT+'/Расчет-%02d'%n;os.mkdir(OUT)
NAMES=['Первичная обмотка','Вторичная обмотка']
RUNS=[];CHECKS=[]
def real(x):return float(complex(x).real)
def pair(z):return [complex(z).real,complex(z).imag]
def relative(x,y):return abs(x-y)/max(abs(y),1e-20)
def check(name,condition,value,limit):CHECKS.append({'id':name,'passed':bool(condition),'value':value,'limit':limit})
def solve(template,tag,currents,frequency=0,secondary_turns=39,primary_turns=354):
    opendocument(BASE+'/'+template+'.fem')
    if secondary_turns!=39:
        for x,sign in [(-42,1),(41,-1)]:
            mi_selectlabel(x,2 if x<0 else 0)
            mi_setblockprop('Медь',0,.5,NAMES[1],0,2,sign*secondary_turns);mi_clearselected()
    if primary_turns!=354:
        for x,y,sign in [(-31,-14,1),(32,-19,-1)]:
            mi_selectlabel(x,y);mi_setblockprop('Медь',0,.5,NAMES[0],0,1,sign*primary_turns);mi_clearselected()
    if secondary_turns!=39 or primary_turns!=354:
        mi_setcomment('Трансформатор: N1='+str(primary_turns)+',N2='+str(secondary_turns)+', глубина43мм, μr2000. Группы1первичная,2вторичная,3магнитопровод,4воздух. Выбор обмоток по напряжению на нагрузке. Сопротивления определены отдельно по геометрии витка и заполнению0.6.')
    if frequency:mi_probdef(frequency,'millimeters','planar',1e-9,43,30,0)
    for k in range(2):mi_modifycircprop(NAMES[k],1,currents[k])
    path=OUT+'/'+tag+'.fem'
    mi_saveas(path);mi_close();opendocument(path)
    mi_analyze(1);mi_loadsolution()
    props=[mo_getcircuitproperties(name) for name in NAMES]
    psi=[complex(p[2]) for p in props]
    mo_clearblock();mo_groupselectblock()
    energy=real(mo_blockintegral(2));coenergy=real(mo_blockintegral(17));mo_clearblock()
    point=mo_getpointvalues(0,0);b=[point[1],point[2]]
    row={'tag':tag,'template':template,'model':tag+'.fem','solution':tag+'.ans','frequency_Hz':frequency,'currents_peak_A':[pair(i) for i in currents],'linkages_peak_Wb':[pair(v) for v in psi],'energy_J':energy,'coenergy_J':coenergy,'B_center_T':[pair(v) for v in b],'elements':mo_numelements()}
    mo_close();mi_close();opendocument(path);mi_loadsolution()
    again=[complex(mo_getcircuitproperties(name)[2]) for name in NAMES]
    row['roundtrip_passed']=all(abs(a-b)<1e-10*max(abs(a),1) for a,b in zip(again,psi))
    mo_close();mi_close()
    check(tag+'-roundtrip',row['roundtrip_passed'],row['roundtrip_passed'],True)
    if frequency==0:
        half=sum(real(currents[k])*psi[k].real for k in range(2))/2
        if template!='Трансформатор-насыщение':check(tag+'-energy',relative(energy,half)<.003,relative(energy,half),.003)
        check(tag+'-positive',energy>=0,energy,0)
    RUNS.append(row);print('TRANSFORMER',tag,psi,energy)
    return psi,row

matrices=[]
for template in ['Трансформатор-сетка-1','Трансформатор-сетка-05','Трансформатор-граница-2']:
    a,_=solve(template,template+'-первичная',[1,0]);b,_=solve(template,template+'-вторичная',[0,1])
    l11,l22,m12,m21=a[0].real,b[1].real,b[0].real,a[1].real
    m=(m12+m21)/2
    matrix={'template':template,'L11_H':l11,'L22_H':l22,'M12_H':m12,'M21_H':m21,'M_H':m,'k':m/math.sqrt(l11*l22),'Lsc_H':l11-m*m/l22}
    matrices.append(matrix)
    check(template+'-reciprocity',relative(m12,m21)<.001,relative(m12,m21),.001)
    check(template+'-definite',l11*l22>m*m and m>0,[l11*l22,m*m],'>')
for key in ['L11_H','L22_H','M_H','Lsc_H']:
    e=relative(matrices[0][key],matrices[1][key]);check('mesh-'+key,e<.025,e,.025)
    e=relative(matrices[1][key],matrices[2][key]);check('boundary-'+key,e<.03,e,.03)
L=matrices[-1];w=2*math.pi*50;U1=230
fill=.6;coil_area=.00065;sigma=58e6
R1=354*.2552/(sigma*fill*coil_area/354)
R2=39*.3392/(sigma*fill*coil_area/39)
z11=R1+1j*w*L['L11_H'];z22=R2+1j*w*L['L22_H'];zm=1j*w*L['M_H']
loads=[]
for resistance in [None,20,10,5,1,24/26.3,0]:
    voltage=abs(z11-zm*zm/z22)*2.75 if resistance==0 else U1
    if resistance is None:i1=U1/z11;i2=0j;tag='Холостой-ход'
    else:
        determinant=z11*(z22+resistance)-zm*zm
        i1=voltage*(z22+resistance)/determinant;i2=-voltage*zm/determinant;tag='Нагрузка-'+str(resistance)
    psi,run=solve('Трансформатор-граница-2',tag,[i1*math.sqrt(2),i2*math.sqrt(2)],50)
    u1field=R1*i1+1j*w*psi[0]/math.sqrt(2)
    u2field=R2*i2+1j*w*psi[1]/math.sqrt(2)
    terminal_expected=0j if resistance==0 else (-resistance*i2 if resistance is not None else zm*i1)
    pin=(voltage*i1.conjugate()).real;pout=0 if resistance is None else resistance*abs(i2)**2
    pcu=R1*abs(i1)**2+R2*abs(i2)**2
    row={'Rload_ohm':resistance,'U1_rms_V':voltage,'I1_rms_A':pair(i1),'I2_rms_A':pair(i2),'U2_rms_V':abs(u2field),'U1_residual_rel':abs(u1field-voltage)/voltage,'U2_residual_primary_ref_rel':abs(u2field-terminal_expected)*354/39/voltage,'Pinput_W':pin,'Pload_W':pout,'Pcu_W':pcu,'balance_W':pin-pout-pcu,'model':run['model'],'solution':run['solution']}
    check(tag+'-U1',row['U1_residual_rel']<.005,row['U1_residual_rel'],.005)
    check(tag+'-U2',row['U2_residual_primary_ref_rel']<.005,row['U2_residual_primary_ref_rel'],.005)
    check(tag+'-power',abs(row['balance_W'])<1e-8*max(pin,1),row['balance_W'],1e-8*max(pin,1))
    loads.append(row)

selected_turns=38
ratio=selected_turns/39
primary_turns=350;primary_ratio=primary_turns/354
selected_R1=R1*primary_ratio*primary_ratio
selected_R2=R2*ratio*ratio
selected_z11=z11*primary_ratio*primary_ratio
selected_zm=zm*ratio*primary_ratio;selected_z22=selected_R2+1j*w*L['L22_H']*ratio*ratio
rl=24/26.3;det=selected_z11*(selected_z22+rl)-selected_zm*selected_zm
selected_i1=U1*(selected_z22+rl)/det;selected_i2=-U1*selected_zm/det
psi,selected_run=solve('Трансформатор-граница-2','Выбранная-обмотка-350-38',[selected_i1*math.sqrt(2),selected_i2*math.sqrt(2)],50,selected_turns,primary_turns)
selected_u2=selected_R2*selected_i2+1j*w*psi[1]/math.sqrt(2)
selected_residual=abs(selected_u2+rl*selected_i2)*primary_turns/selected_turns/U1
check('selected-turns-terminal',selected_residual<.005,selected_residual,.005)
design={'primary_turns':primary_turns,'secondary_turns':selected_turns,'Rload_ohm':rl,'R1_ohm':selected_R1,'R2_ohm':selected_R2,'U2_rms_V':abs(selected_u2),'I2_rms_A':abs(selected_i2),'target_U2_V':24,'target_error_rel':abs(abs(selected_u2)/24-1),'terminal_residual_rel':selected_residual,'model':selected_run['model'],'solution':selected_run['solution']}
check('selected-turns-target',design['target_error_rel']<.01,design['target_error_rel'],.01)

comp_psi,comp_row=solve('Трансформатор-компенсация','Компенсация-рассеяния',[2.75,-354*2.75/39])
leff_mm=159.745
compensation={'I1_A':2.75,'I2_A':-354*2.75/39,'NI_residual_At':354*2.75+39*(-354*2.75/39),'energy_at_1mm_J':comp_row['energy_J'],'effective_length_mm':leff_mm,'Lsigma_estimate_H':2*comp_row['energy_J']*leff_mm/(2.75**2),'model':comp_row['model'],'solution':comp_row['solution'],'scope':'Air compensation model and empirical effective path length; separate approximation, not a 3D field solution'}
check('compensation-NI',abs(compensation['NI_residual_At'])<1e-10,compensation['NI_residual_At'],1e-10)

nonlinear=[]
for current in [.1,.3,.6,1,2]:
    psi,row=solve('Трансформатор-насыщение','Насыщение-'+str(current),[current,0])
    nonlinear.append({'I1_A':current,'Psi1_Wb':psi[0].real,'Psi2_Wb':psi[1].real,'Lsec_H':psi[0].real/current,'Bcenter_T':abs(complex(*row['B_center_T'][1]))})
check('saturation-reduces-L',nonlinear[-1]['Lsec_H']<nonlinear[0]['Lsec_H'],[nonlinear[0]['Lsec_H'],nonlinear[-1]['Lsec_H']],'<')
RESULT={'passed':all(c['passed'] for c in CHECKS),'matrices':matrices,'loads':loads,'design':design,'compensation':compensation,'nonlinear':nonlinear,'runs':RUNS,'checks':CHECKS,'settings':{'N1':354,'N2':39,'depth_mm':43,'linear_mu_r':2000,'frequency_Hz':50,'U1_rms_V':230,'R1_ohm':R1,'R2_ohm':R2,'fill_factor':fill,'coil_area_m2':coil_area,'mean_turn_lengths_m':[.2552,.3392],'sigma_copper_S_per_m':sigma,'losses':'external winding resistance only; no iron, end-region field or dielectric losses'}}
with open(OUT+'/Результаты.json','w') as f:json.dump(RESULT,f)
with open(OUT+'/Нагрузочная-характеристика.csv','w') as f:
    f.write('Rload_ohm;I1_rms_A;I2_rms_A;U2_rms_V;Pinput_W;Pload_W;Pcu_W;U1_residual_rel;U2_residual_primary_ref_rel\n')
    for v in loads:f.write(';'.join(str(x) for x in [v['Rload_ohm'],abs(complex(*v['I1_rms_A'])),abs(complex(*v['I2_rms_A'])),v['U2_rms_V'],v['Pinput_W'],v['Pload_W'],v['Pcu_W'],v['U1_residual_rel'],v['U2_residual_primary_ref_rel']])+'\n')
print('TRANSFORMER_ALL_CHECKS',RESULT['passed'],len(CHECKS))
