import json,os,sys,math
CONFIG_FILE='em200-jobs.json'
def enc(v):
 if isinstance(v,complex):return [v.real,v.imag]
 if isinstance(v,(list,tuple)):return [enc(x) for x in v]
 if isinstance(v,dict):return {k:enc(x) for k,x in v.items()}
 return v
def close():
 for name in ['mo_close','mi_close']:
  try:globals()[name]()
  except Exception:pass
def solve(job):
 close();opendocument(job['source']);p=job['path'];mi_saveas(p+'.fem')
 if job.get('dy_mm'):
  mi_selectgroup(2);mi_movetranslate(0,job['dy_mm'],4);mi_clearselected()
 mi_modifycircprop('Катушка',1,job['current_A'])
 if job.get('mesh_mm'):
  gy=20+job['source_gap_mm']/2;mi_selectlabel(20,gy);mi_setblockprop('Воздух',0,job['mesh_mm'],'',0,0,1);mi_clearselected()
 if job.get('previous_ans'):mi_setprevious(job['previous_ans'],job['previous_type'])
 mi_saveas(p+'.fem');mi_analyze(1);mi_loadsolution();mo_smooth('off')
 out={'job':job,'runtime':sys.version,'implementation':str(sys.implementation),'circuits':{'Катушка':enc(mo_getcircuitproperties('Катушка'))},'nodes':mo_numnodes(),'elements':mo_numelements(),'problem':enc(mo_getprobleminfo())}
 mo_clearblock();mo_groupselectblock();out['energy_J']=enc(mo_blockintegral(2));out['coenergy_J']=enc(mo_blockintegral(17));out['loss_W']=enc(mo_blockintegral(6));mo_clearblock();mo_groupselectblock(2);out['force_N']=enc([mo_blockintegral(18),mo_blockintegral(19)]);mo_clearblock()
 gap=job['source_gap_mm']+job.get('dy_mm',0);out['gap_each_mm']=gap;out['delta_total_mm']=gap*2;out['gap_points']={}
 for name,x in [('left',-20),('right',20)]:out['gap_points'][name]=enc(mo_getpointvalues(x,20+gap/2))
 with open(p+'.json','w') as f:json.dump(enc(out),f)
 close();return out

# Exact relative inputs; refuse overwrite by fresh output directory.
os.mkdir('control-output')
JOBS=[{'source_gap_mm': 1, 'dy_mm': 0, 'current_A': 1, 'source': 'input-721f1c49590d.fem', 'path': 'control-output/nl-g1-I1-dy0', 'id': 'nl-g1-I1-dy0'}, {'source_gap_mm': 1, 'dy_mm': -0.025, 'current_A': 1, 'source': 'input-721f1c49590d.fem', 'path': 'control-output/nl-g1-I1-dy-0.025', 'id': 'nl-g1-I1-dy-0.025'}, {'source_gap_mm': 1, 'dy_mm': 0.025, 'current_A': 1, 'source': 'input-721f1c49590d.fem', 'path': 'control-output/nl-g1-I1-dy0.025', 'id': 'nl-g1-I1-dy0.025'}, {'source_gap_mm': 1, 'source': 'input-721f1c49590d.fem', 'current_A': 1, 'path': 'control-output/increment-base-I1', 'id': 'increment-base-I1'}, {'path': 'control-output/previous-type1-dI0.01', 'id': 'previous-type1-dI0.01', 'current_A': 0.01, 'previous_type': 1, 'source': 'input-721f1c49590d.fem', 'source_gap_mm': 1, 'previous_ans': 'control-output/increment-base-I1.ans'}, {'path': 'control-output/previous-type2-dI0.01', 'id': 'previous-type2-dI0.01', 'current_A': 0.01, 'previous_type': 2, 'source': 'input-721f1c49590d.fem', 'source_gap_mm': 1, 'previous_ans': 'control-output/increment-base-I1.ans'}]
outputs=[]
for job in JOBS:outputs.append(solve(job))
EXPECTED=[{'id': 'nl-g1-I1-dy0', 'Psi_Wb': 0.01223409778389755, 'Fy_N': -4.7423996465828235, 'coenergy_J': 0.0061172425774290995}, {'id': 'nl-g1-I1-dy-0.025', 'Psi_Wb': 0.012476709562730785, 'Fy_N': -4.97404930502572, 'coenergy_J': 0.006238556752531307}, {'id': 'nl-g1-I1-dy0.025', 'Psi_Wb': 0.012002280375943914, 'Fy_N': -4.527206760395439, 'coenergy_J': 0.006001310439672498}, {'id': 'increment-base-I1', 'Psi_Wb': 0.01223409778389755, 'Fy_N': -4.7423996465828235, 'coenergy_J': 0.0061172425774290995}, {'id': 'previous-type1-dI0.01', 'Psi_Wb': 0.00012232515904936543, 'Fy_N': -0.00047411060490084184, 'coenergy_J': 6.115098826157566e-07}, {'id': 'previous-type2-dI0.01', 'Psi_Wb': 0.0001223409778399888, 'Fy_N': -0.0004742399645271486, 'coenergy_J': 6.116664388843998e-07}]
replay_checks=[]
for result,target in zip(outputs,EXPECTED):
 for label,actual,expected,atol in [('Psi',result['circuits']['Катушка'][2],target['Psi_Wb'],1e-10),('Fy',result['force_N'][1],target['Fy_N'],1e-5),('coenergy',result['coenergy_J'],target['coenergy_J'],1e-9)]:
  tolerance=atol+1e-5*abs(expected);replay_checks.append({'label':target['id']+' '+label,'actual':actual,'expected':expected,'tolerance':tolerance,'passed':abs(actual-expected)<=tolerance})
with open('original-00.py') as f:exec(f.read(),globals())
passed=all(x['passed'] for x in replay_checks)
with open('replay-result.json','w') as f:json.dump({'passed':passed,'checks':replay_checks,'nativeFreshFieldCount':len(outputs)},f)
assert passed,'Exact native electromagnet replay mismatch'
