Coverage for src / tracekit / analyzers / power / conduction.py: 100%

63 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 23:04 +0000

1"""Conduction loss analysis for TraceKit. 

2 

3Provides conduction loss calculations for power semiconductor devices 

4during their on-state. 

5 

6 

7Example: 

8 >>> from tracekit.analyzers.power.conduction import conduction_loss 

9 >>> p_cond = conduction_loss(v_on_trace, i_d_trace, duty_cycle=0.5) 

10 >>> print(f"Conduction loss: {p_cond:.2f} W") 

11""" 

12 

13from __future__ import annotations 

14 

15from typing import TYPE_CHECKING 

16 

17import numpy as np 

18 

19from tracekit.core.exceptions import AnalysisError 

20 

21if TYPE_CHECKING: 

22 from tracekit.core.types import WaveformTrace 

23 

24 

25def conduction_loss( 

26 voltage: WaveformTrace, 

27 current: WaveformTrace, 

28 duty_cycle: float | None = None, 

29) -> float: 

30 """Calculate conduction loss during on-state. 

31 

32 P_cond = V_on * I_on * D (steady state) 

33 or 

34 P_cond = mean(V(t) * I(t)) over on-state periods 

35 

36 Args: 

37 voltage: On-state voltage trace (V_ds(on) or V_ce(sat)). 

38 current: On-state current trace. 

39 duty_cycle: Duty cycle (0 to 1). If None, calculates from waveforms. 

40 

41 Returns: 

42 Conduction loss in Watts. 

43 

44 Example: 

45 >>> p_cond = conduction_loss(v_on, i_d, duty_cycle=0.5) 

46 >>> print(f"Conduction loss: {p_cond:.2f} W") 

47 

48 References: 

49 Infineon Application Note AN-9010 

50 """ 

51 v_data = voltage.data 

52 i_data = current.data 

53 

54 # Ensure same length 

55 min_len = min(len(v_data), len(i_data)) 

56 v_data = v_data[:min_len] 

57 i_data = i_data[:min_len] 

58 

59 if duty_cycle is not None: 

60 # Use average values and duty cycle 

61 v_on = float(np.mean(v_data)) 

62 i_on = float(np.mean(i_data)) 

63 return v_on * i_on * duty_cycle 

64 else: 

65 # Calculate instantaneous power and average 

66 power = v_data * i_data 

67 return float(np.mean(power)) 

68 

69 

70def on_resistance( 

71 voltage: WaveformTrace, 

72 current: WaveformTrace, 

73 *, 

74 min_current: float | None = None, 

75 min_current_fraction: float = 0.1, 

76) -> float: 

77 """Calculate on-state resistance (R_ds(on) or R_ce(sat)). 

78 

79 R_on = V_on / I_on 

80 

81 Args: 

82 voltage: On-state voltage trace. 

83 current: On-state current trace. 

84 min_current: Minimum current threshold (to avoid division by small values). 

85 If None, uses min_current_fraction * peak current. 

86 min_current_fraction: Fraction of peak current to use as minimum threshold 

87 when min_current is None (default: 0.1 = 10% of peak). 

88 

89 Returns: 

90 On-state resistance in Ohms. 

91 

92 Example: 

93 >>> r_on = on_resistance(v_ds, i_d) 

94 >>> print(f"R_ds(on): {r_on*1e3:.2f} mOhm") 

95 >>> # With tighter threshold (5% of peak): 

96 >>> r_on = on_resistance(v_ds, i_d, min_current_fraction=0.05) 

97 """ 

98 v_data = voltage.data 

99 i_data = current.data 

100 

101 min_len = min(len(v_data), len(i_data)) 

102 v_data = v_data[:min_len] 

103 i_data = i_data[:min_len] 

104 

105 # Filter by minimum current 

106 if min_current is None: 

107 min_current = min_current_fraction * np.max(np.abs(i_data)) 

108 

109 mask = np.abs(i_data) >= min_current 

110 if not np.any(mask): 

111 return np.nan # type: ignore[no-any-return] 

112 

113 v_on = v_data[mask] 

114 i_on = i_data[mask] 

115 

116 # Linear fit to get resistance 

117 # V = I * R -> slope is R 

118 coeffs = np.polyfit(i_on, v_on, 1) 

119 return float(coeffs[0]) 

120 

121 

122def forward_voltage( 

123 voltage: WaveformTrace, 

124 current: WaveformTrace, 

125 *, 

126 current_threshold: float | None = None, 

127 current_threshold_fraction: float = 0.1, 

128 threshold_window: float = 0.1, 

129) -> float: 

130 """Calculate forward voltage drop (for diodes or IGBT V_ce(sat)). 

131 

132 Extracts the voltage at a reference current level. 

133 

134 Args: 

135 voltage: Forward voltage trace. 

136 current: Forward current trace. 

137 current_threshold: Current level at which to measure Vf. 

138 If None, uses current_threshold_fraction * peak current. 

139 current_threshold_fraction: Fraction of peak current to use as threshold 

140 when current_threshold is None (default: 0.1 = 10% of peak). 

141 threshold_window: Tolerance window around the current threshold, 

142 as a fraction of the threshold (default: 0.1 = +/-10% window). 

143 Samples within this window are averaged to compute Vf. 

144 

145 Returns: 

146 Forward voltage in Volts. 

147 

148 Example: 

149 >>> vf = forward_voltage(v_f, i_f) 

150 >>> print(f"Forward voltage: {vf:.2f} V") 

151 >>> # With tighter window for more precise measurement: 

152 >>> vf = forward_voltage(v_f, i_f, threshold_window=0.05) 

153 """ 

154 v_data = voltage.data 

155 i_data = current.data 

156 

157 min_len = min(len(v_data), len(i_data)) 

158 v_data = v_data[:min_len] 

159 i_data = i_data[:min_len] 

160 

161 if current_threshold is None: 

162 current_threshold = current_threshold_fraction * np.max(np.abs(i_data)) 

163 

164 # Find samples near the current threshold 

165 near_threshold = np.abs(i_data - current_threshold) < threshold_window * current_threshold 

166 if not np.any(near_threshold): 

167 # Interpolate 

168 idx = np.argmin(np.abs(i_data - current_threshold)) 

169 return float(v_data[idx]) 

170 

171 return float(np.mean(v_data[near_threshold])) 

172 

173 

174def duty_cycle_weighted_loss( 

175 losses: list[tuple[float, float]], 

176) -> float: 

177 """Calculate total loss from multiple operating points. 

178 

179 Useful for variable duty cycle or multi-mode operation. 

180 

181 Args: 

182 losses: List of (loss_watts, duty_cycle) tuples. 

183 

184 Returns: 

185 Total weighted average loss in Watts. 

186 

187 Raises: 

188 AnalysisError: If total duty cycle exceeds 1.0 

189 

190 Example: 

191 >>> # 10W at 30% duty, 5W at 50% duty 

192 >>> total = duty_cycle_weighted_loss([(10, 0.3), (5, 0.5)]) 

193 """ 

194 total = 0.0 

195 total_duty = 0.0 

196 

197 for loss, duty in losses: 

198 total += loss * duty 

199 total_duty += duty 

200 

201 if total_duty > 1.0: 

202 raise AnalysisError(f"Total duty cycle exceeds 1.0: {total_duty}") 

203 

204 return total 

205 

206 

207def temperature_derating( 

208 r_on_25c: float, 

209 temperature: float, 

210 temp_coefficient: float = 0.004, 

211) -> float: 

212 """Calculate temperature-derated on-resistance. 

213 

214 R_on(T) = R_on(25C) * (1 + alpha * (T - 25)) 

215 

216 Args: 

217 r_on_25c: On-resistance at 25C in Ohms. 

218 temperature: Operating temperature in Celsius. 

219 temp_coefficient: Temperature coefficient (default 0.4%/C for Si MOSFET). 

220 

221 Returns: 

222 Derated on-resistance in Ohms. 

223 

224 Example: 

225 >>> r_on_100c = temperature_derating(0.010, 100) # 10mOhm at 25C 

226 >>> print(f"R_on at 100C: {r_on_100c*1e3:.2f} mOhm") 

227 """ 

228 return r_on_25c * (1 + temp_coefficient * (temperature - 25)) 

229 

230 

231def mosfet_conduction_loss( 

232 i_rms: float, 

233 r_ds_on: float, 

234 temperature: float = 25.0, 

235 temp_coefficient: float = 0.004, 

236) -> float: 

237 """Calculate MOSFET conduction loss. 

238 

239 P_cond = I_rms^2 * R_ds(on) 

240 

241 Args: 

242 i_rms: RMS drain current in Amps. 

243 r_ds_on: On-state resistance at 25C in Ohms. 

244 temperature: Junction temperature in Celsius. 

245 temp_coefficient: Temperature coefficient. 

246 

247 Returns: 

248 Conduction loss in Watts. 

249 

250 Example: 

251 >>> p = mosfet_conduction_loss(i_rms=10, r_ds_on=0.010, temperature=100) 

252 """ 

253 r_on = temperature_derating(r_ds_on, temperature, temp_coefficient) 

254 return i_rms**2 * r_on 

255 

256 

257def diode_conduction_loss( 

258 i_avg: float, 

259 i_rms: float, 

260 v_f: float, 

261 r_d: float = 0.0, 

262) -> float: 

263 """Calculate diode conduction loss. 

264 

265 P_cond = V_f * I_avg + r_d * I_rms^2 

266 

267 Args: 

268 i_avg: Average forward current in Amps. 

269 i_rms: RMS forward current in Amps. 

270 v_f: Forward voltage drop in Volts. 

271 r_d: Dynamic resistance in Ohms. 

272 

273 Returns: 

274 Conduction loss in Watts. 

275 

276 Example: 

277 >>> p = diode_conduction_loss(i_avg=5, i_rms=7, v_f=0.7, r_d=0.01) 

278 """ 

279 return v_f * i_avg + r_d * i_rms**2 

280 

281 

282def igbt_conduction_loss( 

283 i_c: float, 

284 v_ce_sat: float, 

285 r_c: float = 0.0, 

286) -> float: 

287 """Calculate IGBT conduction loss. 

288 

289 P_cond = V_ce(sat) * I_c + r_c * I_c^2 

290 

291 Args: 

292 i_c: Collector current in Amps. 

293 v_ce_sat: Collector-emitter saturation voltage in Volts. 

294 r_c: Collector resistance in Ohms. 

295 

296 Returns: 

297 Conduction loss in Watts. 

298 

299 Example: 

300 >>> p = igbt_conduction_loss(i_c=50, v_ce_sat=2.0, r_c=0.01) 

301 """ 

302 return v_ce_sat * i_c + r_c * i_c**2 

303 

304 

305__all__ = [ 

306 "conduction_loss", 

307 "diode_conduction_loss", 

308 "duty_cycle_weighted_loss", 

309 "forward_voltage", 

310 "igbt_conduction_loss", 

311 "mosfet_conduction_loss", 

312 "on_resistance", 

313 "temperature_derating", 

314]