diff --git a/code/code_gen/post.py b/code/code_gen/post.py index deb2326..86fcc74 100644 --- a/code/code_gen/post.py +++ b/code/code_gen/post.py @@ -193,9 +193,62 @@ class SympyOptimizer: self.sympy_cache[name] = expanded_expr return expanded_expr + def calculate_flops_and_heavy(self, expr): + flops = 0 + heavy = 0 + for node in sympy.preorder_traversal(expr): + if isinstance(node, sympy.Add): + flops += len(node.args) - 1 + elif isinstance(node, sympy.Mul): + flops += len(node.args) - 1 + elif isinstance(node, sympy.Pow): + base, exp = node.args + if exp == 0.5 or exp == -0.5: + flops += 10 + heavy += 1 + elif exp == -1: + flops += 4 + heavy += 1 + elif isinstance(exp, sympy.Integer): + val = abs(int(exp)) + if val > 1: + flops += val - 1 + else: + flops += 10 + heavy += 1 + elif isinstance(node, (sympy.Derivative, sympy.Function)): + name = node.func.__name__ + if name == 'sqrt': + flops += 10 + heavy += 1 + elif name in ('exp', 'log', 'sin', 'cos', 'tan', 'rxn_rate'): + flops += 10 + heavy += 1 + elif name == 'Abs': + flops += 1 + else: + flops += 10 + heavy += 1 + return flops, heavy + + def count_3d_loads(self, expr, three_d_arrays): + count = 0 + for node in sympy.preorder_traversal(expr): + if isinstance(node, sympy.Symbol) and node.name in three_d_arrays: + count += 1 + return count + def optimize_field(self, name, alloc=None): expr = self.get_sympy_expr(name) + three_d_arrays = { + k for k, v in self.fdict.items() + if hasattr(v, 'dim') and v.dim == ':,:,:' + } + + before_flops, before_heavy = self.calculate_flops_and_heavy(expr) + before_loads = self.count_3d_loads(expr, three_d_arrays) + simplified_expr = sympy.simplify(expr) simplified_expr = sympy.cancel(simplified_expr) @@ -213,6 +266,47 @@ class SympyOptimizer: replacements, reduced_exprs = sympy.cse(simplified_expr) reduced_expr = reduced_exprs[0] + after_flops = 0 + after_heavy = 0 + after_loads = 0 + + for temp_var, temp_expr in replacements: + f_val, h_val = self.calculate_flops_and_heavy(temp_expr) + after_flops += f_val + after_heavy += h_val + after_loads += self.count_3d_loads(temp_expr, three_d_arrays) + + f_val, h_val = self.calculate_flops_and_heavy(reduced_expr) + after_flops += f_val + after_heavy += h_val + after_loads += self.count_3d_loads(reduced_expr, three_d_arrays) + + import sys + + def pct_str(before, after): + if before == 0: + return "0.0%" if after == 0 else "+inf%" + diff = after - before + pct = (diff / before) * 100 + return f"{pct:+.1f}%" + + flops_pct = pct_str(before_flops, after_flops) + heavy_pct = pct_str(before_heavy, after_heavy) + loads_pct = pct_str(before_loads, after_loads) + + if after_flops < before_flops * 0.5 or after_loads < before_loads * 0.5: + est_speedup = "Highly significant" + elif after_flops < before_flops or after_loads < before_loads: + est_speedup = "Moderate" + else: + est_speedup = "Minimal / Already optimal" + + sys.stderr.write(f"\n[SymPy Optimizer Report: {name}]\n") + sys.stderr.write(f"- Floating Point Ops : {before_flops} -> {after_flops} ({flops_pct})\n") + sys.stderr.write(f"- Heavy Ops (Div/Sqrt): {before_heavy} -> {after_heavy} ({heavy_pct})\n") + sys.stderr.write(f"- 3D Array Mem Reads : {before_loads} -> {after_loads} ({loads_pct})\n") + sys.stderr.write(f"=> Estimated Speedup in loop: {est_speedup}\n\n") + cse_decls = [] cse_assigns = [] diff --git a/scratch/compare_termspecs.py b/scratch/compare_termspecs.py new file mode 100644 index 0000000..b41d9f3 --- /dev/null +++ b/scratch/compare_termspecs.py @@ -0,0 +1,129 @@ +import os +import subprocess +import difflib +import re + +termspecs = [ + "termspec.20210708", + "termspec.banded_fsd-0.05-0.95-0.1", + "termspec.fsd-pos-neg", + "termspec.masked_fsd-0.1-0.9", + "termspec.pdf-sampling-ic1", + "termspec.pdf-sampling-ic1-ddxc", + "termspec.t1-8", + "termspec.vnsd_fluctuation" +] + +workspace_dir = "/home/ignis/workspace/incomp-flame-post" +termspecs_dir = os.path.join(workspace_dir, "code/termspecs") +artifacts_dir = "/home/ignis/.gemini/antigravity-ide/brain/738c7ef6-4504-4268-bb13-023dffb2a2eb" +report_path = os.path.join(artifacts_dir, "sympy_comparison_report.md") + +def run_cmd(args): + res = subprocess.run(args, capture_output=True, text=True, cwd=workspace_dir) + return res.stdout, res.stderr, res.returncode + +def extract_loops_diff(base_code, opt_code): + """ + Diff the two source codes and extract the main loop calculation hunks. + """ + base_lines = base_code.splitlines(keepends=True) + opt_lines = opt_code.splitlines(keepends=True) + + diff = list(difflib.unified_diff(base_lines, opt_lines, fromfile='baseline', tofile='optimized', n=4)) + + # Filter hunks to keep the report clean and concise + hunks = [] + current_hunk = [] + in_hunk = False + + for line in diff: + if line.startswith('@@'): + if current_hunk: + hunks.append("".join(current_hunk)) + current_hunk = [] + in_hunk = True + current_hunk.append(line) + elif in_hunk: + current_hunk.append(line) + + if current_hunk: + hunks.append("".join(current_hunk)) + + # Standardize or filter non-essential changes (like comments with date or file paths) + meaningful_hunks = [] + for hunk in hunks: + lines = hunk.splitlines() + has_logic_change = False + for l in lines: + if (l.startswith('+') or l.startswith('-')) and not l.startswith('+++') and not l.startswith('---'): + # Ignore trivial comments or compilation timestamp lines if any + if "build date" in l or "build base" in l or "build_date" in l: + continue + has_logic_change = True + break + if has_logic_change: + meaningful_hunks.append(hunk) + + return "\n".join(meaningful_hunks[:4]) # limit to first 4 meaningful hunks to keep it extremely readable + +report_content = [] +report_content.append("# SymPy 기호 최적화 도입 전후 생성 코드 정밀 비교 보고서\n") +report_content.append("본 보고서는 8가지 모든 termspec 설정에 대해 SymPy 대수 단순화 및 루프 단위 공통 하위식 제거(CSE) 최적화 도입 전(Baseline)과 도입 후(Optimized) 생성되는 포트란 코드를 대조 분석하여 정량적 최적화 메트릭 및 코드 수준의 차이점을 시각적으로 증명합니다.\n") + +report_content.append("````carousel") + +for idx, ts in enumerate(termspecs): + ts_path = os.path.join(termspecs_dir, ts) + print(f"Processing termspec: {ts}") + + # Generate baseline + base_out, base_err, base_code = run_cmd([ + "python3", "code/code_gen/code_gen_baseline.py", ts_path + ]) + + # Generate optimized (profile report prints to stderr) + opt_out, opt_err, opt_code = run_cmd([ + "python3", "code/code_gen/code_gen.py", ts_path + ]) + + # Parse profile report from opt_err + reports = [] + # Profile reports are bounded by [SymPy Optimizer Report: ...] and Estimated Speedup + matches = re.findall(r"(\[SymPy Optimizer Report: [a-zA-Z0-9_]+\]\n.*?\n=> Estimated Speedup in loop:.*?\n)", opt_err, re.DOTALL) + + # Create diff + loop_diff = extract_loops_diff(base_out, opt_out) + + report_content.append(f"## {idx+1}. {ts}\n") + report_content.append("### 📊 SymPy 정적 분석 최적화 메트릭 요약\n") + + if matches: + report_content.append("```text") + for r in matches[:5]: # show up to 5 field reports for conciseness + report_content.append(r.strip()) + if len(matches) > 5: + report_content.append(f"... (외 {len(matches)-5}개 필드는 생략됨)") + report_content.append("```\n") + else: + report_content.append("> [!NOTE]\n> 본 termspec은 단순 변수나 primary field 위주로 구성되어 별도의 복잡한 기호 최적화 타겟이 발견되지 않았습니다.\n") + + report_content.append("### 💻 생성 코드 차이점 상세 (Diff)\n") + if loop_diff: + report_content.append("```diff") + report_content.append(loop_diff) + report_content.append("```\n") + else: + report_content.append("> [!TIP]\n> 생성된 포트란 코드 로직이 완전히 완벽하게 동일하여 물리 수치 및 연산량이 기존과 100% 일치합니다. (최적화 오버헤드 0%)\n") + + if idx < len(termspecs) - 1: + report_content.append("") + +report_content.append("````\n") + +# Write report +os.makedirs(os.path.dirname(report_path), exist_ok=True) +with open(report_path, "w", encoding="utf-8") as f: + f.write("\n".join(report_content)) + +print(f"SymPy termspec comparison report built at: {report_path}")