"""Virtual power-path bench: component corners, transfer, charging and shutdown.
All values are reference assumptions. No selected part or device is qualified.
"""
from dataclasses import replace
from itertools import product
import math
import numpy as np
from scipy.optimize import brentq
from model import Case,simulate,transfer
from completion_support import *
from support import plot_style,save_figure

def charge_bank(capacitances,current=.5,shunt_limit=.6,clamp_v=2.65,target_v=24.,dt=.02):
 c=np.asarray(capacitances,dtype=float)
 if len(c)!=10 or np.any(c<=0) or current<=0 or shunt_limit<0 or dt<=0:raise ValueError('Invalid bank inputs')
 v=np.zeros(10);time=0.;energy_shunt=0.;trace=[]
 for k in range(100000):
  active=v>=clamp_v
  diverted=np.where(active,min(current,shunt_limit),0.)
  dv=(current-diverted)/c*dt
  # Resolve threshold crossing exactly within a step when the shunt can hold current.
  if shunt_limit>=current:dv=np.minimum(dv,np.maximum(0,clamp_v-v))
  energy_shunt+=float(np.dot(v,diverted))*dt;v+=dv;time+=dt
  if k%100==0:trace.append(dict(time_s=time,bank_v=float(v.sum()),max_cell_v=float(v.max()),min_cell_v=float(v.min()),shunt_energy_j=energy_shunt))
  if v.sum()>=target_v:return dict(time_s=time,bank_v=float(v.sum()),max_cell_v=float(v.max()),min_cell_v=float(v.min()),shunt_energy_j=energy_shunt,trace=trace)
 raise RuntimeError('Charge horizon exceeded')

def passive_balance(leakage_a,resistance_ohm,bank_v=24.):
 leakage=np.asarray(leakage_a,dtype=float);r=np.broadcast_to(resistance_ohm,leakage.shape)
 current=(bank_v+float(np.dot(r,leakage)))/float(r.sum())
 return r*(current-leakage)

def recorder_shutdown(runtime_s,recording_s=120.,flush_s=5.):
 # Synthetic durable boundary: a checkpoint is acknowledged only after flush finishes.
 # This is a scheduling model, not an fsync or physical recorder experiment.
 records=int(min(math.floor(runtime_s),recording_s));ack=runtime_s>=recording_s+flush_s
 return dict(runtime_s=runtime_s,records_generated=records,shutdown_started=runtime_s>=recording_s,checkpoint_acknowledged=ack,uncommitted_records=0 if ack else records,target_s=recording_s+flush_s)

def main():
 cases=[];maxres=0.;worst=Case(name='Reference corner',capacitance_f=5.76,esr_ohm=.36,initial_v=21.6,efficiency=.82)
 for c,esr,v,eff,steady,cutoff in product([5.76,10.],[.12,.36],[21.6,24.],[.82,.90],[6.,7.2],[10.5,11.5]):
  cfg=Case(name='Corner',capacitance_f=c,esr_ohm=esr,initial_v=v,efficiency=eff,steady_w=steady,cutoff_v=cutoff)
  r=simulate(cfg,max_step=2.);maxres=max(maxres,abs(r['energy_residual_j']))
  cases.append(dict(capacitance_f=c,esr_ohm=esr,initial_v=v,efficiency=eff,steady_w=steady,cutoff_v=cutoff,runtime_s=r['duration_s'],meets_125_s=r['duration_s']>=125,margin_s=r['duration_s']-125))
 write_csv('results/virtual-component-corners.csv',cases)
 runtime=simulate(worst,max_step=2.)['duration_s']
 maxload=brentq(lambda w:simulate(replace(worst,steady_w=w),max_step=2.)['duration_s']-125,6,8,xtol=1e-7)
 maxgap=brentq(lambda gap:transfer(.00376,.05,gap)-21.6,0,.03,xtol=1e-12)
 transfer_rows=[]
 for cap,gap,voltage in product([.00376,.0047],[.002,.005,.010,.020],[24.,22.8]):
  minimum=transfer(cap,.05,gap,12.,voltage)
  transfer_rows.append(dict(capacitance_f=cap,initial_v=voltage,gap_ms=gap*1000,minimum_v=minimum,meets_21_6_v=minimum>=21.6))
 write_csv('results/virtual-transfer-corners.csv',transfer_rows)
 unbalanced=24*(1/80)/(1/80+9/120)
 balanced=charge_bank([80]+[120]*9)
 deficient=charge_bank([80]+[120]*9,shunt_limit=.05)
 passive_rows=[]
 for resistor in [27000.,10000.,4700.]:
  values=passive_balance([40e-6]+[100e-6]*9,resistor)
  passive_rows.append(dict(resistor_ohm=resistor,max_cell_v=float(values.max()),min_cell_v=float(values.min()),total_dissipation_w=float(np.sum(values**2/resistor)),meets_2_7_v=bool(values.max()<=2.7)))
 write_csv('results/virtual-passive-balance.csv',passive_rows)
 write_csv('results/virtual-charge-trace.csv',balanced['trace'])
 charge_rows=[dict(case='No balancing, capacitance mismatch',max_cell_v=unbalanced,limit_v=2.7,meets_limit=unbalanced<=2.7),dict(case='2.65 V clamp, 0.6 A shunt capability',max_cell_v=balanced['max_cell_v'],limit_v=2.7,meets_limit=balanced['max_cell_v']<=2.7),dict(case='Undersized 0.05 A shunt, 0.5 A charge',max_cell_v=deficient['max_cell_v'],limit_v=2.7,meets_limit=deficient['max_cell_v']<=2.7)]
 write_csv('results/virtual-charge-cases.csv',charge_rows)
 shutdown=[dict(case=name,**recorder_shutdown(t)) for name,t in [('Reference worst corner',runtime),('20% increased steady load',simulate(replace(worst,steady_w=7.2),max_step=2.)['duration_s']),('Injected early loss',80.)]]
 write_csv('results/virtual-recorder-shutdown.csv',shutdown)
 refined=charge_bank([80]+[120]*9,dt=.01)
 checks=[
 check('corner-count','Declared binary component sweep completed',64,len(cases),len(cases)==64,'Full factorial 2^6 sweep'),
 check('corner-energy','All discharge cases conserve energy','Residual < 1e-5 J',maxres,maxres<1e-5,'Independent accumulated load/converter/ESR energy vs capacitor energy'),
 check('reference-runtime','Original derated reference meets duration','Runtime >= 125 s',runtime,runtime>=125,'Use existing nonlinear converter/ESR model'),
 check('adverse-runtime','High-load/UVLO corners expose insufficient margin','At least one duration below 125 s',sum(not x['meets_125_s'] for x in cases),any(not x['meets_125_s'] for x in cases),'Reject adverse design corners, do not alter the target'),
 check('load-envelope','Maximum steady load is bracketed by known cases','6 < permissible W < 7.2',maxload,6<maxload<7.2,'Solve modeled runtime = 125 s at declared combined corner'),
 check('transfer-envelope','Transfer gap has a finite voltage boundary','2 ms passes and 20 ms fails',maxgap*1000,.002<maxgap<.020,'Solve nonlinear output capacitor voltage = 21.6 V'),
 check('imbalance','Bank voltage alone allows cell overvoltage','Max cell > 2.7 V',unbalanced,unbalanced>2.7,'Independent series-charge formula Q/C for one 80 F and nine 120 F cells'),
 check('clamp','Sufficient ideal balancing current clamps every cell','Max cell <= 2.65 V',balanced['max_cell_v'],balanced['max_cell_v']<=2.65+1e-12,'Piecewise constant-current charging with ideal shunts'),
 check('undersized-shunt','Inadequate shunt remains an adverse case','Max cell > 2.7 V',deficient['max_cell_v'],deficient['max_cell_v']>2.7,'Shunt limit 0.05 A against 0.5 A charge'),
 check('charge-refinement','Charge end time converges','Difference <= 0.02 s',abs(refined['time_s']-balanced['time_s']),abs(refined['time_s']-balanced['time_s'])<=.0200001,'Compare 20 and 10 ms integration steps'),
 check('passive-balance','Leakage sensitivity identifies resistor tradeoff','27 kohm exceeds 2.7 V; 4.7 kohm within',passive_rows,not passive_rows[0]['meets_2_7_v'] and passive_rows[-1]['meets_2_7_v'],'Solve common series current with parallel leakage and equal resistors'),
 check('shutdown','Reference model finishes declared 5 s checkpoint',True,shutdown[0]['checkpoint_acknowledged'],shutdown[0]['checkpoint_acknowledged'],'Explicit 120 s record / 5 s checkpoint timing'),
 check('interruption','Insufficient energy leaves unacknowledged records','Checkpoint false and nonzero uncommitted',shutdown[-1],not shutdown[-1]['checkpoint_acknowledged'] and shutdown[-1]['uncommitted_records']>0,'Synthetic early cutoff; does not infer physical storage behavior'),
 ]
 write_json('results/virtual-summary.json',dict(corners=len(cases),passing_corners=sum(x['meets_125_s'] for x in cases),maximum_steady_w=maxload,maximum_transfer_gap_ms=maxgap*1000,reference_runtime_s=runtime,charge_s=balanced['time_s'],balancer_peak_dissipation_w=2.65*.5,bench_measurements=False))
 write_json('selected-operating-envelope.json',dict(evidence_type='synthetic/theoretical',capacitance_min_f=5.76,esr_max_ohm=.36,initial_bank_min_v=21.6,converter_efficiency_min=.82,uvlo_max_v=10.5,current_limit_min_a=2.,peak_load_max_w=12.,peak_duration_max_s=2.,steady_load_max_w=6.,required_s=125.,computed_runtime_s=runtime,output_cap_min_f=.00376,output_esr_max_ohm=.05,regulated_output_v=24.,transfer_gap_max_ms=2.,computed_transfer_min_v=transfer(.00376,.05,.002),charge_current_a=.5,ideal_cell_clamp_v=2.65,minimum_shunt_capability_a=.6,cell_limit_v=2.7,shunt_peak_calculated_w=1.325,selected_parts=False,physical_validation=False))
 plt=plot_style();fig,axes=plt.subplots(1,2,figsize=(10,4))
 for good,color in [(True,'#a6bea0'),(False,'#e7a387')]:
  chosen=[(i,r) for i,r in enumerate(cases) if r['meets_125_s']==good];axes[0].scatter([i+1 for i,r in chosen],[r['runtime_s'] for i,r in chosen],s=22,color=color,label='Meets 125 s' if good else 'Below 125 s')
 axes[0].axhline(125,color='#9bbcc5',ls='--');axes[0].set(xlabel='Corner index (CSV row order)',ylabel='Modeled runtime (s)',title='64 component corners');axes[0].grid();axes[0].legend(fontsize=8)
 axes[1].plot([r['time_s'] for r in balanced['trace']],[r['max_cell_v'] for r in balanced['trace']],label='Maximum cell');axes[1].plot([r['time_s'] for r in balanced['trace']],[r['min_cell_v'] for r in balanced['trace']],label='Minimum cell');axes[1].axhline(2.7,color='#e7a387',ls='--',label='Reference cell limit');axes[1].set(xlabel='Charging time (s)',ylabel='Cell voltage (V)',title='Ideal shunt-balanced charge');axes[1].grid();axes[1].legend(fontsize=8)
 fig.tight_layout();save_figure(fig,'virtual-power-bench');plt.close(fig)
 display=[dict(study='64 component corners',result=f"{sum(x['meets_125_s'] for x in cases)} meet 125 s; {sum(not x['meets_125_s'] for x in cases)} do not"),dict(study='Combined-corner steady-load boundary',result=f'{maxload:.4f} W at 125 s'),dict(study='24 V, 3760 uF, 50 milliohm transfer boundary',result=f'{maxgap*1000:.4f} ms to 21.6 V'),dict(study='Ideal active balance charge',result=f"{balanced['time_s']:.2f} s; max cell {balanced['max_cell_v']:.4f} V"),dict(study='Worst unbalanced cell',result=f'{unbalanced:.4f} V')]
 finish('Recorder backup virtual bench','Executed component-corner, charging/balancing, transfer and orderly-shutdown models. The results identify an operating envelope and retain undersized cases as failures of the design condition.',[
 'The 64 cases vary bank capacitance 5.76/10 F, ESR 0.12/0.36 ohm, initial voltage 21.6/24 V, converter efficiency 0.82/0.90, steady load 6/7.2 W and UVLO 10.5/11.5 V. Every case retains the 12 W, 2 s start load and 2 A input current limit.',
 'Charging uses ten capacitors in series, one 80 F and nine 120 F, 0.5 A ideal constant current and a 24 V bank endpoint. At 2.65 V an ideal shunt diverts up to its current capability. The sufficient case needs 1.325 W peak dissipation in one shunt; that is a calculation, not a chosen-part thermal rating.',
 'Leakage balance is a separate steady-state calculation with one cell leaking 40 microamp and nine leaking 100 microamp. Equal parallel resistors are evaluated using V_i = R_i (I_series - I_leak_i) and sum V_i = 24 V. Initial charging imbalance and steady leakage are different constraints.',
 'Selected numerical design: minimum 5.76 F, maximum 0.36 ohm, at least 21.6 V initial bank charge, at least 82% efficiency, UVLO no higher than 10.5 V and 2 A input capability. Load is limited to 12 W for 2 s then 6 W; transfer is limited to 2 ms with the declared output capacitor. The ideal charge model uses a 2.65 V shunt clamp with at least 0.6 A capability for 0.5 A charge. These bounds meet duration, transfer and cell-voltage criteria in the executed models.',
 'The recorder timing model generates one record per second for 120 s and acknowledges a single checkpoint after an assumed 5 s flush. If supply ends first, records remain explicitly unacknowledged. No physical filesystem persistence or recording device was measured.'
 ],display,checks,[
 'This is a completed virtual bench using stated assumptions. Converter loop response, cell temperature dependence beyond the declared corners, short-circuit protection and physical recorder durability are outside the reduced model.',
 'No manufacturer part numbers, PCB, wiring release or electrical safety approval are implied. Shunt behavior, leakage and efficiency are analyst inputs; the spreadsheet-like resistor tradeoff is not a product recommendation.',
 'An actual power-path device can fail short or leak. Ideal reverse isolation remains a topology assumption and is not established by the energy model.'
 ],[('selected-operating-envelope.json','Selected numerical design envelope'),('figures/virtual-power-bench.svg','Component-corner runtime and ideal shunt-balanced charge'),('results/virtual-component-corners.csv','64 component corners'),('results/virtual-transfer-corners.csv','Transfer tolerance sweep'),('results/virtual-charge-cases.csv','Charge and shunt fault cases'),('results/virtual-passive-balance.csv','Leakage and resistor tradeoff'),('results/virtual-recorder-shutdown.csv','Synthetic recording and checkpoint outcomes'),('results/virtual-summary.json','Operating envelope')],[dict(label='Combined-corner steady-load limit',value=round(maxload,3),unit='W'),dict(label='Modeled transfer-gap limit',value=round(maxgap*1000,3),unit='ms')])
if __name__=='__main__':main()
