Coverage for src\time_series_analyzer\formatters.py: 35%

223 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-23 11:57 +0800

1""" 

2输出格式化模块 

3 

4支持多种输出格式:LaTeX数学表达式、纯文本、JSON结构化数据等。 

5适用于报告和论文引用。 

6""" 

7 

8import json 

9from typing import Dict, Any, Optional, List, Union 

10from datetime import datetime 

11import sympy as sp 

12from sympy import latex, pretty 

13 

14from .models import ARIMAModel, SeasonalARIMAModel 

15from .transfer_function import TransferFunction, TransferFunctionDeriver 

16 

17 

18class OutputFormatter: 

19 """输出格式化器""" 

20 

21 def __init__(self, precision: int = 4): 

22 """ 

23 初始化格式化器 

24  

25 Args: 

26 precision: 数值精度 

27 """ 

28 self.precision = precision 

29 

30 def format_latex(self, model: Union[ARIMAModel, SeasonalARIMAModel], 

31 include_transfer_function: bool = True, 

32 include_analysis: bool = False) -> str: 

33 """ 

34 生成LaTeX格式的输出 

35  

36 Args: 

37 model: 时间序列模型 

38 include_transfer_function: 是否包含传递函数 

39 include_analysis: 是否包含分析结果 

40  

41 Returns: 

42 LaTeX格式的字符串 

43 """ 

44 latex_output = [] 

45 

46 # 文档头部 

47 latex_output.append("\\documentclass{article}") 

48 latex_output.append("\\usepackage{amsmath}") 

49 latex_output.append("\\usepackage{amssymb}") 

50 latex_output.append("\\usepackage{amsfonts}") 

51 latex_output.append("\\begin{document}") 

52 latex_output.append("") 

53 

54 # 模型标题 

55 latex_output.append(f"\\section{{{model.name} 模型分析}}") 

56 latex_output.append("") 

57 

58 # 模型定义 

59 latex_output.append("\\subsection{模型定义}") 

60 

61 if isinstance(model, SeasonalARIMAModel): 

62 latex_output.append(self._format_sarima_latex(model)) 

63 else: 

64 latex_output.append(self._format_arima_latex(model)) 

65 

66 latex_output.append("") 

67 

68 # 传递函数 

69 if include_transfer_function: 

70 latex_output.append("\\subsection{传递函数}") 

71 deriver = TransferFunctionDeriver() 

72 transfer_func = deriver.derive_transfer_function(model) 

73 latex_output.append(self._format_transfer_function_latex(transfer_func)) 

74 latex_output.append("") 

75 

76 # 分析结果 

77 if include_analysis: 

78 latex_output.append("\\subsection{稳定性分析}") 

79 deriver = TransferFunctionDeriver() 

80 stability = deriver.analyze_stability(model) 

81 latex_output.append(self._format_stability_latex(stability)) 

82 latex_output.append("") 

83 

84 latex_output.append("\\end{document}") 

85 

86 return "\n".join(latex_output) 

87 

88 def _format_arima_latex(self, model: ARIMAModel) -> str: 

89 """格式化ARIMA模型的LaTeX表示""" 

90 lines = [] 

91 

92 # 模型方程 

93 if model.p > 0 and model.q > 0: 

94 # 完整的ARIMA方程 

95 ar_terms = [] 

96 for i, param in enumerate(model.ar_params): 

97 if isinstance(param, (int, float)): 

98 if param >= 0: 

99 ar_terms.append(f"{param:.{self.precision}f}X_{{t-{i+1}}}") 

100 else: 

101 ar_terms.append(f"{param:.{self.precision}f}X_{{t-{i+1}}}") 

102 else: 

103 ar_terms.append(f"\\phi_{{{i+1}}}X_{{t-{i+1}}}") 

104 

105 ma_terms = [] 

106 for i, param in enumerate(model.ma_params): 

107 if isinstance(param, (int, float)): 

108 if param >= 0: 

109 ma_terms.append(f"+{param:.{self.precision}f}\\varepsilon_{{t-{i+1}}}") 

110 else: 

111 ma_terms.append(f"{param:.{self.precision}f}\\varepsilon_{{t-{i+1}}}") 

112 else: 

113 ma_terms.append(f"+\\theta_{{{i+1}}}\\varepsilon_{{t-{i+1}}}") 

114 

115 if model.d > 0: 

116 lines.append("差分后的序列:") 

117 lines.append(f"$\\nabla^{{{model.d}}}X_t = X_t - " + " - ".join(ar_terms) + 

118 " = \\varepsilon_t " + "".join(ma_terms) + "$") 

119 else: 

120 lines.append("模型方程:") 

121 lines.append(f"$X_t - " + " - ".join(ar_terms) + 

122 " = \\varepsilon_t " + "".join(ma_terms) + "$") 

123 

124 # 滞后算子形式 

125 lines.append("") 

126 lines.append("滞后算子形式:") 

127 

128 # AR多项式 

129 if model.p > 0: 

130 ar_poly_latex = self._polynomial_to_latex(model.get_ar_polynomial()) 

131 lines.append(f"$\\phi(B) = {ar_poly_latex}$") 

132 

133 # MA多项式 

134 if model.q > 0: 

135 ma_poly_latex = self._polynomial_to_latex(model.get_ma_polynomial()) 

136 lines.append(f"$\\theta(B) = {ma_poly_latex}$") 

137 

138 # 差分算子 

139 if model.d > 0: 

140 lines.append(f"$(1-B)^{{{model.d}}}X_t = \\theta(B)\\varepsilon_t$") 

141 

142 return "\n".join(lines) 

143 

144 def _format_sarima_latex(self, model: SeasonalARIMAModel) -> str: 

145 """格式化SARIMA模型的LaTeX表示""" 

146 lines = [] 

147 

148 lines.append("季节性ARIMA模型方程:") 

149 lines.append("") 

150 

151 # 滞后算子形式 

152 components = [] 

153 

154 if model.p > 0: 

155 components.append("\\phi(B)") 

156 if model.P > 0: 

157 components.append(f"\\Phi(B^{{{model.m}}})") 

158 if model.d > 0: 

159 components.append(f"(1-B)^{{{model.d}}}") 

160 if model.D > 0: 

161 components.append(f"(1-B^{{{model.m}}})^{{{model.D}}}") 

162 

163 left_side = "".join(components) + "X_t" 

164 

165 right_components = [] 

166 if model.q > 0: 

167 right_components.append("\\theta(B)") 

168 if model.Q > 0: 

169 right_components.append(f"\\Theta(B^{{{model.m}}})") 

170 

171 right_side = "".join(right_components) + "\\varepsilon_t" 

172 

173 lines.append(f"${left_side} = {right_side}$") 

174 lines.append("") 

175 

176 # 各多项式定义 

177 if model.p > 0: 

178 ar_poly_latex = self._polynomial_to_latex(model.get_ar_polynomial()) 

179 lines.append(f"$\\phi(B) = {ar_poly_latex}$") 

180 

181 if model.q > 0: 

182 ma_poly_latex = self._polynomial_to_latex(model.get_ma_polynomial()) 

183 lines.append(f"$\\theta(B) = {ma_poly_latex}$") 

184 

185 if model.P > 0: 

186 seasonal_ar_latex = self._polynomial_to_latex(model.get_seasonal_ar_polynomial()) 

187 lines.append(f"$\\Phi(B^{{{model.m}}}) = {seasonal_ar_latex}$") 

188 

189 if model.Q > 0: 

190 seasonal_ma_latex = self._polynomial_to_latex(model.get_seasonal_ma_polynomial()) 

191 lines.append(f"$\\Theta(B^{{{model.m}}}) = {seasonal_ma_latex}$") 

192 

193 return "\n".join(lines) 

194 

195 def _polynomial_to_latex(self, poly) -> str: 

196 """将多项式转换为LaTeX格式""" 

197 try: 

198 return latex(poly.as_expr()) 

199 except: 

200 return str(poly) 

201 

202 def _format_transfer_function_latex(self, transfer_func: TransferFunction) -> str: 

203 """格式化传递函数的LaTeX表示""" 

204 lines = [] 

205 

206 lines.append("传递函数定义:") 

207 lines.append("") 

208 

209 num_latex = latex(transfer_func.numerator.as_expr()) 

210 den_latex = latex(transfer_func.denominator.as_expr()) 

211 

212 lines.append(f"$H(B) = \\frac{{{num_latex}}}{{{den_latex}}}$") 

213 lines.append("") 

214 

215 # 极点和零点 

216 poles = transfer_func.get_poles() 

217 zeros = transfer_func.get_zeros() 

218 

219 if poles: 

220 lines.append("极点:") 

221 for i, pole in enumerate(poles): 

222 if abs(pole.imag) < 1e-10: 

223 lines.append(f"$p_{{{i+1}}} = {pole.real:.{self.precision}f}$") 

224 else: 

225 lines.append(f"$p_{{{i+1}}} = {pole.real:.{self.precision}f} {'+' if pole.imag >= 0 else ''}{pole.imag:.{self.precision}f}i$") 

226 

227 if zeros: 

228 lines.append("") 

229 lines.append("零点:") 

230 for i, zero in enumerate(zeros): 

231 if abs(zero.imag) < 1e-10: 

232 lines.append(f"$z_{{{i+1}}} = {zero.real:.{self.precision}f}$") 

233 else: 

234 lines.append(f"$z_{{{i+1}}} = {zero.real:.{self.precision}f} {'+' if zero.imag >= 0 else ''}{zero.imag:.{self.precision}f}i$") 

235 

236 return "\n".join(lines) 

237 

238 def _format_stability_latex(self, stability: Dict[str, Any]) -> str: 

239 """格式化稳定性分析的LaTeX表示""" 

240 lines = [] 

241 

242 is_stable = stability.get("is_stable", False) 

243 max_magnitude = stability.get("max_pole_magnitude", 0) 

244 

245 lines.append(f"系统稳定性: {'稳定' if is_stable else '不稳定'}") 

246 lines.append("") 

247 lines.append(f"最大极点模长: ${max_magnitude:.{self.precision}f}$") 

248 

249 if not is_stable: 

250 lines.append("") 

251 lines.append("\\textbf{警告:} 系统不稳定,存在模长大于等于1的极点。") 

252 

253 return "\n".join(lines) 

254 

255 def format_plain_text(self, model: Union[ARIMAModel, SeasonalARIMAModel], 

256 include_transfer_function: bool = True, 

257 include_analysis: bool = False) -> str: 

258 """ 

259 生成纯文本格式的输出 

260  

261 Args: 

262 model: 时间序列模型 

263 include_transfer_function: 是否包含传递函数 

264 include_analysis: 是否包含分析结果 

265  

266 Returns: 

267 纯文本格式的字符串 

268 """ 

269 output = [] 

270 

271 # 标题 

272 output.append("=" * 50) 

273 output.append(f"{model.name} 模型分析") 

274 output.append("=" * 50) 

275 output.append("") 

276 

277 # 模型参数 

278 output.append("模型参数:") 

279 output.append(f" p (AR阶数): {model.p}") 

280 output.append(f" d (差分阶数): {model.d}") 

281 output.append(f" q (MA阶数): {model.q}") 

282 

283 if isinstance(model, SeasonalARIMAModel): 

284 output.append(f" P (季节性AR阶数): {model.P}") 

285 output.append(f" D (季节性差分阶数): {model.D}") 

286 output.append(f" Q (季节性MA阶数): {model.Q}") 

287 output.append(f" m (季节性周期): {model.m}") 

288 

289 output.append("") 

290 

291 # 参数值 

292 if model.ar_params: 

293 output.append("AR参数:") 

294 for i, param in enumerate(model.ar_params): 

295 output.append(f" φ_{i+1} = {param}") 

296 

297 if model.ma_params: 

298 output.append("MA参数:") 

299 for i, param in enumerate(model.ma_params): 

300 output.append(f" θ_{i+1} = {param}") 

301 

302 if isinstance(model, SeasonalARIMAModel): 

303 if model.seasonal_ar_params: 

304 output.append("季节性AR参数:") 

305 for i, param in enumerate(model.seasonal_ar_params): 

306 output.append(f" Φ_{i+1} = {param}") 

307 

308 if model.seasonal_ma_params: 

309 output.append("季节性MA参数:") 

310 for i, param in enumerate(model.seasonal_ma_params): 

311 output.append(f" Θ_{i+1} = {param}") 

312 

313 output.append("") 

314 

315 # 传递函数 

316 if include_transfer_function: 

317 output.append("传递函数:") 

318 output.append("-" * 30) 

319 deriver = TransferFunctionDeriver() 

320 transfer_func = deriver.derive_transfer_function(model) 

321 

322 output.append(f"H(B) = ({transfer_func.numerator.as_expr()}) / ({transfer_func.denominator.as_expr()})") 

323 output.append("") 

324 

325 # 极点和零点 

326 poles = transfer_func.get_poles() 

327 zeros = transfer_func.get_zeros() 

328 

329 if poles: 

330 output.append("极点:") 

331 for i, pole in enumerate(poles): 

332 output.append(f" p_{i+1} = {pole:.{self.precision}f}") 

333 

334 if zeros: 

335 output.append("零点:") 

336 for i, zero in enumerate(zeros): 

337 output.append(f" z_{i+1} = {zero:.{self.precision}f}") 

338 

339 output.append("") 

340 

341 # 稳定性分析 

342 if include_analysis: 

343 output.append("稳定性分析:") 

344 output.append("-" * 30) 

345 deriver = TransferFunctionDeriver() 

346 stability = deriver.analyze_stability(model) 

347 

348 is_stable = stability.get("is_stable", False) 

349 max_magnitude = stability.get("max_pole_magnitude", 0) 

350 

351 output.append(f"系统稳定性: {'稳定' if is_stable else '不稳定'}") 

352 output.append(f"最大极点模长: {max_magnitude:.{self.precision}f}") 

353 

354 if not is_stable: 

355 output.append("警告: 系统不稳定,存在模长大于等于1的极点。") 

356 

357 output.append("") 

358 

359 return "\n".join(output) 

360 

361 def format_json(self, model: Union[ARIMAModel, SeasonalARIMAModel], 

362 include_transfer_function: bool = True, 

363 include_analysis: bool = False) -> str: 

364 """ 

365 生成JSON格式的输出 

366  

367 Args: 

368 model: 时间序列模型 

369 include_transfer_function: 是否包含传递函数 

370 include_analysis: 是否包含分析结果 

371  

372 Returns: 

373 JSON格式的字符串 

374 """ 

375 result = { 

376 "timestamp": datetime.now().isoformat(), 

377 "model": model.to_dict() 

378 } 

379 

380 if include_transfer_function: 

381 deriver = TransferFunctionDeriver() 

382 transfer_func = deriver.derive_transfer_function(model) 

383 

384 result["transfer_function"] = { 

385 "numerator": str(transfer_func.numerator.as_expr()), 

386 "denominator": str(transfer_func.denominator.as_expr()), 

387 "poles": [{"real": pole.real, "imag": pole.imag} for pole in transfer_func.get_poles()], 

388 "zeros": [{"real": zero.real, "imag": zero.imag} for zero in transfer_func.get_zeros()] 

389 } 

390 

391 if include_analysis: 

392 deriver = TransferFunctionDeriver() 

393 stability = deriver.analyze_stability(model) 

394 

395 # 转换复数为字典格式 

396 result["stability_analysis"] = { 

397 "is_stable": stability["is_stable"], 

398 "max_pole_magnitude": stability["max_pole_magnitude"], 

399 "stability_margin": stability["stability_margin"], 

400 "poles": [{"real": pole.real, "imag": pole.imag} for pole in stability["poles"]], 

401 "zeros": [{"real": zero.real, "imag": zero.imag} for zero in stability["zeros"]] 

402 } 

403 

404 return json.dumps(result, indent=2, ensure_ascii=False)