# Run inside native FEMM5. Every path is relative to this supplied folder.
import os
def _path_exists(path):
    try:
        os.stat(path)
        return True
    except OSError:
        return False
if _path_exists('files/reproduce.py'):
    os.chdir('files')
elif '__file__' in globals() and '/' in __file__.replace('\\','/'):
    os.chdir(__file__.replace('\\','/').rsplit('/',1)[0])
import json,os,sys,time,math,cmath
import numpy as np
CONFIG={'output': 'source-map-rebuilt.json', 'directories': {'practice-ii08-1': '.'}, 'models': {'./compact-source-model.fem': {'problem': 'planar', 'depth': 80, 'angle': 25, 'circuits': ['Фаза А', 'Фаза В', 'Фаза С']}}}
DATA={'runtime':sys.version,'implementation':str(sys.implementation),'experiments':{},'field_solutions':0}
assert sys.implementation.name=='micropython'
def save():
    with open(CONFIG['output'],'w') as f:json.dump(DATA,f)
def serial(v):
    if isinstance(v,complex):return [v.real,v.imag]
    if isinstance(v,(tuple,list)):return [serial(x) for x in v]
    if isinstance(v,dict):return {k:serial(x) for k,x in v.items()}
    return v
def close():
    for n in ('mo_close','mi_close'):
        try:globals()[n]()
        except Exception:pass
def start(name):
    close();d=CONFIG['directories'][name];os.chdir(d);return d
def model(name):return CONFIG['directories'][name]+'/source-model.fem'
def solve(path,tag,currents=None,frequency=None,rotate=0,group=2,mesh=None,material=None):
    close();opendocument(path);mi_saveas(os.getcwd()+'/'+tag+'.fem')
    if frequency is not None:
        info=CONFIG['models'][path]
        mi_probdef(frequency,'millimeters',info['problem'],1e-8,info['depth'],info['angle'])
    if rotate:
        mi_selectgroup(group);mi_moverotate(0,0,rotate);mi_clearselected()
    if material:
        x,y,material_name=material;mi_selectlabel(x,y);mi_setblockprop(material_name,0,1,'',0,0,0);mi_clearselected()
    if mesh:
        for x,y,mat,sz,circ,mag,g,turns in mesh:
            mi_selectlabel(x,y);mi_setblockprop(mat,0,sz,circ,mag,g,turns);mi_clearselected()
    if currents:
        for name,value in currents:mi_modifycircprop(name,1,value)
    mi_analyze(1);mi_loadsolution();DATA['field_solutions']+=1
    info={'file':os.getcwd()+'/'+tag+'.fem','nodes':mo_numnodes(),'elements':mo_numelements(),'circuits':{}}
    for name in CONFIG['models'][path]['circuits']:
        info['circuits'][name]=list(mo_getcircuitproperties(name))
    mo_clearblock();mo_groupselectblock();info['energy_J']=mo_blockintegral(2);info['coenergy_J']=mo_blockintegral(17)
    info['loss_magnetic_W']=mo_blockintegral(3);info['loss_resistive_W']=mo_blockintegral(4);info['loss_total_W']=mo_blockintegral(6)
    mo_clearblock()
    try:
        mo_groupselectblock(group);info['torque_Nm']=mo_blockintegral(22);info['force_x_N']=mo_blockintegral(18);info['force_y_N']=mo_blockintegral(19);mo_clearblock()
    except Exception as error:info['force_error']=str(error)
    return info
def real(x):return complex(x).real
def add(name,value):DATA['experiments'][name]=serial(value);save()
def z_matrix(path,prefix,names):
    columns=[];evidence=[]
    for j in range(len(names)):
        row=solve(path,prefix+'-basis-'+str(j),[(n,1 if j==k else 0) for k,n in enumerate(names)])
        columns.append([complex(row['circuits'][n][1]) for n in names]);evidence.append(row)
    return [[columns[j][i] for j in range(len(names))] for i in range(len(names))],evidence
def linear_load(path,prefix,names,Z,rloads,rprimary,source,neutral=False):
    n=len(names);m=n+1 if neutral else n
    matrix=[[0j for j in range(m)] for i in range(m)]
    rhs=[0j]*m
    for i in range(n):
        for j in range(n):matrix[i][j]=Z[i][j]
        if i<n//2:matrix[i][i]+=rprimary;rhs[i]=source[i]
        else:
            matrix[i][i]+=rloads[i-n//2]
            if neutral:matrix[i][n]=1
    if neutral:
        for j in range(n//2,n):matrix[n][j]=1
    answer=np.linalg.solve(np.array(matrix),np.array(rhs));currents=[complex(answer[j]) for j in range(n)]
    row=solve(path,prefix,[(n,currents[j]) for j,n in enumerate(names)])
    voltages=[complex(row['circuits'][n][1]) for n in names]
    residual=[]
    for i in range(n):
        r=voltages[i]+(rprimary if i<n//2 else rloads[i-n//2])*currents[i]-(source[i] if i<n//2 else 0)
        if neutral and i>=n//2:r+=complex(answer[n])
        residual.append(r)
    row['current_A']=currents;row['voltage_V']=voltages;row['residual_V']=residual
    row['source_power_W']=sum(real(source[i]*currents[i].conjugate())/2 for i in range(n//2))
    row['external_primary_copper_W']=sum(rprimary*abs(currents[i])**2/2 for i in range(n//2))
    row['load_power_W']=sum(rloads[i-n//2]*abs(currents[i])**2/2 for i in range(n//2,n))
    row['neutral_current_A']=-sum(currents[n//2:]);row['neutral_potential_V']=complex(answer[n]) if neutral else 0
    row['power_balance_W']=row['source_power_W']-row['external_primary_copper_W']-row['load_power_W']-real(row['loss_total_W'])
    return row

try:
    name='practice-ii08-1';start(name);path=CONFIG['directories'][name]+'/compact-source-model.fem';names=CONFIG['models'][path]['circuits']
    currents=[0.,1.,1.9,1.95,2.,2.05,2.1,3.,4.,5.,6.,7.,7.9,7.95,8.,8.05,8.1,9.,10.]
    rows=[];DATA['experiments']['srm_map']={'rows':rows,'angles_deg':[2.5*k for k in range(37)],'currents_A':currents,'status':'running','mechanical_period_deg':90.,'source_rotor_offset_deg':-15.,'BH_T_Am':[[0,0],[.5,100],[1,250],[1.4,600],[1.6,1500],[1.8,6000],[2,20000]],'extrapolation':'forbidden'}
    for k in range(37):
        angle=k*2.5
        for index,current in enumerate(currents):
            tag='control-'+str(k)+'-'+str(index) if (k,index) in ((0,4),(6,4),(18,14),(36,18)) else 'transient-current'
            r=solve(path,tag,[(n,current if j==0 else 0) for j,n in enumerate(names)],rotate=angle)
            r['mechanical_angle_deg']=angle;r['requested_current_A']=current;r['case_id']='a'+str(k)+'-i'+str(index)
            if tag=='transient-current':r.pop('file');r['replay_source']=path
            rows.append(serial(r))
        save()
    DATA['experiments']['srm_map']['status']='complete'
    DATA['experiments']['srm_two_phase']=serial(solve(path,'two-phase-control',[(names[0],2),(names[1],2),(names[2],0)],rotate=22.5))
    save()
except Exception as error:
    DATA['fatal_error']=str(error);DATA['fatal_error_type']=type(error).__name__;save()
finally:close()
save()
print('NATIVE_SRM_SOLUTIONS',DATA['field_solutions'])

# Strict complete-map receipt
_m=DATA.get('experiments',{}).get('srm_map',{})
_checks=[{'label':'complete703pointmap plus independenttwo-phasecontrol','passed':DATA.get('field_solutions',0)==704 and _m.get('status')=='complete','actual':DATA.get('field_solutions',0),'expected':704}]
_ok='fatal_error' not in DATA and all(_q['passed'] for _q in _checks)
with open('replay-result.json','w') as _f:json.dump({'passed':_ok,'checks':_checks},_f)
assert _ok,'Full field map incomplete'
