diff --git a/code/code_gen/code_gen.py b/code/code_gen/code_gen.py index a0fbc05..0662779 100644 --- a/code/code_gen/code_gen.py +++ b/code/code_gen/code_gen.py @@ -24,9 +24,19 @@ args = parser.parse_args() class VersionInfo(object): + """Encapsulates build and version metadata for the code generator execution. - def __init__ (self, input_raw): + This class queries the current Git repository state to fetch the HEAD commit + hash and checks if there are uncommitted changes, appending metadata to + identify the exact codebase state used to generate the output files. + """ + def __init__(self, input_raw): + """Initializes VersionInfo with build date, git hash, and terms metadata. + + Args: + input_raw (str): The raw input string containing DSL term definitions. + """ bd = 'build date: {}' bb = 'build base: {}' @@ -54,16 +64,27 @@ class VersionInfo(object): self.term_info = tinfo.format(input_raw) - def fparams (self): + def fparams(self): + """Formats the collected build parameters into a single string. + + Returns: + str: Multi-line string summarizing build date, revision, and terms details. + """ return "\n".join([self.build_date, self.build_base, self.off_base, self.term_info]) - def compile_terms(terms_raw): - """Parses DSL terms spec and runs it through compiler stages 1-4. + """Parses DSL terms spec and runs it through the four compiler stages. + + This function initializes a new `CompilationContext` and executes the compiler pipeline + sequentially: parsing, derivative/fluctuation expansion, data dependency resolution, + and SymPy optimization (including buffer pooling). + + Args: + terms_raw (str): The raw string contents of the DSL terms specification input file. Returns: - ctx (CompilationContext): The compiled context. + CompilationContext: The fully compiled and optimized compilation context. """ ctx = CompilationContext() ParserStage().execute(terms_raw, ctx) @@ -74,7 +95,14 @@ def compile_terms(terms_raw): def get_latex_equations_str(ctx): - """Generates the LaTeX equations dictionary string from the context.""" + """Generates the LaTeX equations dictionary string from the context. + + Args: + ctx (CompilationContext): The compiled context containing the averaged fields. + + Returns: + str: A formatted string representing a Python dictionary mapping field names to LaTeX code. + """ latex_lines = ["{"] for avg in ctx.averaged.values(): latex_lines.append(' "{}" : r"${}$",'.format(avg.name, avg.latex)) @@ -86,8 +114,13 @@ def test(terms_raw, report=False, latex=False): """Compiles the post-processing term specifications from DSL into targets. This compiles the Lark AST through all 4 pipeline stages - and outputs the resulting Fortran source code via FortranProgramWriter, + and outputs the resulting Fortran source code via FortranProgramWriter, a LaTeX equation dictionary via LatexWriter, or an IR report via ReportWriter. + + Args: + terms_raw (str): The raw string contents of the DSL terms specification. + report (bool, optional): If True, prints a JSON-based compilation IR report instead. Defaults to False. + latex (bool, optional): If True, prints LaTeX equation dictionary instead. Defaults to False. """ ctx = compile_terms(terms_raw) @@ -99,9 +132,18 @@ def test(terms_raw, report=False, latex=False): FortranProgramWriter().write(ctx) - def escape_fortran_string(s): - """Escapes string content for standard Fortran source formatting.""" + """Escapes string content for standard Fortran source formatting. + + Converts internal double quotes into double-double quotes and splits the string lines, + wrapping them in Fortran concatenation syntax `// char(10) // &` to respect line length limit. + + Args: + s (str): The raw string to escape. + + Returns: + str: The escaped and formatted Fortran string parameter literal. + """ escaped = s.replace('"', '""') lines = escaped.splitlines() if not lines: @@ -111,7 +153,14 @@ def escape_fortran_string(s): def generate_build_info_module(terms_raw): - """Generates a complete standard Fortran module containing build info and LaTeX equations.""" + """Generates a complete standard Fortran module containing build info and LaTeX equations. + + The generated module contains metadata about the compiler run, including the build base Git hash, + and a string representations of the LaTeX equations. + + Args: + terms_raw (str): The raw string contents of the DSL terms specification input file. + """ vinfo = VersionInfo(terms_raw) build_info_str = vinfo.fparams() diff --git a/code/code_gen/post.py b/code/code_gen/post.py index 2000966..11db601 100644 --- a/code/code_gen/post.py +++ b/code/code_gen/post.py @@ -32,22 +32,59 @@ from sympy.printing.fortran import FCodePrinter # --- Registries for SOLID (OCP) Compliance --- class FunctionRegistry: + """Registry to map mathematical DSL functions to SymPy constructors and LaTeX representations. + + This class enables SOLID Open-Closed Principle (OCP) compliance by decoupling core parsing + logic from mathematical functions, allowing new functions to be added without modifying the parser. + """ + def __init__(self): + """Initializes FunctionRegistry with empty SymPy and LaTeX mappings.""" self._sympy_registry = {} self._latex_registry = {} def register_sympy(self, name, sympy_builder): + """Registers a handler to convert a DSL function to a SymPy representation. + + Args: + name (str): The name of the DSL function. + sympy_builder (callable): A callable mapping function arguments to a SymPy object. + """ self._sympy_registry[name] = sympy_builder def register_latex(self, name, latex_builder): + """Registers a handler to convert a DSL function to a LaTeX math representation. + + Args: + name (str): The name of the DSL function. + latex_builder (callable): A callable mapping arguments to a LaTeX string. + """ self._latex_registry[name] = latex_builder def to_sympy(self, name, *args): + """Converts a function call to its corresponding SymPy expression. + + Args: + name (str): The name of the function. + *args: Arguments to pass to the sympy builder. + + Returns: + sympy.Expr: SymPy node representation of the function call. + """ if name in self._sympy_registry: return self._sympy_registry[name](*args) return sympy.Function(name)(*args) def to_latex(self, name, *args): + """Converts a function call to its corresponding LaTeX representation. + + Args: + name (str): The name of the function. + *args (str): Already-formatted LaTeX strings of the arguments. + + Returns: + str: The LaTeX math representation of the function call. + """ if name in self._latex_registry: return self._latex_registry[name](*args) b = ", ".join(args) @@ -55,6 +92,7 @@ class FunctionRegistry: return r"{}{{({})}}".format(name, b) return r"\mathrm{{{}}}({})".format(name, b) + function_registry = FunctionRegistry() # Register standard mathematical functions @@ -72,13 +110,33 @@ function_registry.register_latex(r"\omega", lambda *args: r"\omega{{({})}}".form class DifferentialOperatorRegistry: + """Registry for spatial differential operators mapping operators to LaTeX representations. + + Handles default and custom differential operators (e.g., ddx, d2dy) used during derivation expansion. + """ + def __init__(self): + """Initializes DifferentialOperatorRegistry with empty operator mappings.""" self._operators = {} def register(self, op_name, latex_symbol): + """Registers a custom LaTeX representation for a given operator. + + Args: + op_name (str): The differential operator name (e.g., 'ddx'). + latex_symbol (str): The corresponding LaTeX math symbol. + """ self._operators[op_name] = latex_symbol def get_latex_symbol(self, op_name): + """Retrieves the LaTeX representation of a differential operator. + + Args: + op_name (str): The name of the operator. + + Returns: + str: The LaTeX code for the differential operator (e.g. '\\partial_{x}'). + """ if op_name in self._operators: return self._operators[op_name] # Fallback to dynamic parsing matching original code @@ -86,6 +144,7 @@ class DifferentialOperatorRegistry: coord = op_name[-1] if op_name else "" return fmt.format(coord + coord if len(op_name) > 3 else coord) + differential_operator_registry = DifferentialOperatorRegistry() # Register standard derivative operators @@ -96,7 +155,25 @@ differential_operator_registry.register("d2dy", r"\partial_{yy}") differential_operator_registry.register("ddz", r"\partial_{z}") differential_operator_registry.register("d2dz", r"\partial_{zz}") + class FortranTemplateStore: + """Static store containing Jinja2 code templates for generating Fortran source blocks. + + Attributes: + REAL_ARRAY_LOOP (str): Standard 3D loop for calculating derived variables, integrating SymPy CSE blocks. + FLUCTUATION_ARRAY_LOOP (str): Loop for calculating fluctuation fields (with averages subtracted). + AVG_ARRAY_SUM (str): Accumulator loop to sum grid values for spatial averaging. + AVG_ARRAY_DIVIDE (str): Global MPI MPI_ALLREDUCE and divide step to finalize statistics. + FMT_DECL_SUBARRAY (str): MPI subarray-based I/O file handle and type variable declarations. + FMT_INIT_SUBARRAY (str): Initialization block for creating MPI file handles and commit subarray types. + FMT_FINAL_SUBARRAY (str): Cleanup code block to close and release MPI types and file handles. + FMT_CALC_SUBARRAY (str): Actual high-performance parallel MPI file writing using subarray layout. + FMT_DECL_LEGACY (str): Declarations for legacy, process-local buffer-based MPI exports. + FMT_INIT_LEGACY (str): Allocation and initialization for legacy buffer arrays. + FMT_FINAL_LEGACY (str): Finalization and deallocation for legacy buffer arrays. + FMT_CALC_LEGACY (str): Data slicing and local buffer writing for legacy export routines. + AVG_ARRAY_WRITE (str): Serial file writing structure for exporting 1D averaged data and derivatives. + """ REAL_ARRAY_LOOP = """ ! {{ comment }} {% if decls_str -%} @@ -256,55 +333,157 @@ close (200) class FortranCodeGenerator(object): + """Visitor implementation that decouples code generation from domain AST node details. + + Implements a classic visitor dispatch pattern that matches class names of the Field objects + to their code generation routines (e.g. generate_code, generate_decl, generate_alloc, generate_free). + """ + def __init__(self, fdict): + """Initializes FortranCodeGenerator with the variable registry dictionary. + + Args: + fdict (dict): Dictionary mapping variable names to their corresponding FieldBase objects. + """ self.fdict = fdict def generate_code(self, field, alloc=None): + """Dispatches visitor to generate actual calculation code for the field. + + Args: + field (FieldBase): The field node to generate code for. + alloc (dict, optional): Buffer allocation map for array pooling. Defaults to None. + + Returns: + str: Generated Fortran statement(s) inside loop blocks. + """ method_name = 'visit_' + field.__class__.__name__ + '_code' visitor = getattr(self, method_name, self.generic_code) return visitor(field, alloc) def generate_decl(self, field): + """Dispatches visitor to generate variable type declarations. + + Args: + field (FieldBase): The field node to generate declaration for. + + Returns: + str: Fortran variable declaration statement. + """ method_name = 'visit_' + field.__class__.__name__ + '_decl' visitor = getattr(self, method_name, self.generic_decl) return visitor(field) def generate_alloc(self, field): + """Dispatches visitor to generate dynamic memory allocation statements. + + Args: + field (FieldBase): The field node to allocate. + + Returns: + str: Fortran allocate and initialization statements. + """ method_name = 'visit_' + field.__class__.__name__ + '_alloc' visitor = getattr(self, method_name, self.generic_alloc) return visitor(field) def generate_free(self, field): + """Dispatches visitor to generate deallocation statements. + + Args: + field (FieldBase): The field node to deallocate. + + Returns: + str: Fortran deallocate statements. + """ method_name = 'visit_' + field.__class__.__name__ + '_free' visitor = getattr(self, method_name, self.generic_free) return visitor(field) def generate_avg(self, field): + """Dispatches visitor to generate average accumulators. + + Args: + field (FieldBase): The averaged field node. + + Returns: + str: Fortran summation and normalization statements. + """ method_name = 'visit_' + field.__class__.__name__ + '_avg' visitor = getattr(self, method_name, self.generic_avg) return visitor(field) # --- Generic Fallbacks --- def generic_code(self, field, alloc=None): + """Generic fallback for code generation. + + Args: + field (FieldBase): The field node. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: Empty string. + """ return "" def generic_decl(self, field): + """Generic fallback for declaration code generation. + + Args: + field (FieldBase): The field node. + + Returns: + str: Standard allocatable real 3D/1D array declaration. + """ real_array_decl = "real(real64), allocatable, dimension({1}) :: {0}" return real_array_decl.format(field.name, field.dim) def generic_alloc(self, field): + """Generic fallback for dynamic allocation. + + Args: + field (FieldBase): The field node. + + Returns: + str: Fortran allocate statement. + """ return make_allocate(field.name, field.shape) def generic_free(self, field): + """Generic fallback for deallocation. + + Args: + field (FieldBase): The field node. + + Returns: + str: Fortran deallocate statement. + """ real_array_free = "deallocate({})" return real_array_free.format(field.name) def generic_avg(self, field): + """Generic fallback for average code generation. + + Args: + field (FieldBase): The field node. + + Returns: + str: Empty string. + """ return "" # --- Visit Methods --- def visit_FieldExporter_code(self, exporter, alloc=None): + """Generates Fortran code for exporting fields using parallel MPI-IO. + + Args: + exporter (FieldExporter): Exporter metadata. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: MPI writing code block. + """ exporter.params["work_array"] = exporter.parent.array if exporter.use_subarray: return Template(FortranTemplateStore.FMT_CALC_SUBARRAY).render(**exporter.params) @@ -312,24 +491,57 @@ class FortranCodeGenerator(object): return Template(FortranTemplateStore.FMT_CALC_LEGACY).render(**exporter.params) def visit_FieldExporter_decl(self, exporter): + """Generates declarations for MPI-IO handles. + + Args: + exporter (FieldExporter): Exporter metadata. + + Returns: + str: Declarations block. + """ if exporter.use_subarray: return Template(FortranTemplateStore.FMT_DECL_SUBARRAY).render(**exporter.params) else: return Template(FortranTemplateStore.FMT_DECL_LEGACY).render(**exporter.params) def visit_FieldExporter_alloc(self, exporter): + """Generates initialization code for MPI-IO types and file opens. + + Args: + exporter (FieldExporter): Exporter metadata. + + Returns: + str: Initialization block. + """ if exporter.use_subarray: return Template(FortranTemplateStore.FMT_INIT_SUBARRAY).render(**exporter.params) else: return Template(FortranTemplateStore.FMT_INIT_LEGACY).render(**exporter.params) def visit_FieldExporter_free(self, exporter): + """Generates finalization code for MPI-IO types and file closes. + + Args: + exporter (FieldExporter): Exporter metadata. + + Returns: + str: MPI-IO finalization statements. + """ if exporter.use_subarray: return Template(FortranTemplateStore.FMT_FINAL_SUBARRAY).render(**exporter.params) else: return Template(FortranTemplateStore.FMT_FINAL_LEGACY).render(**exporter.params) def visit_Field_code(self, field, alloc=None): + """Generates calculation code for normal derived fields with SymPy CSE optimization. + + Args: + field (Field): The calculated field. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: Renders optimized 3D loop including CSE declarations. + """ field.array = alloc[field.name] if alloc else field.name opt = SympyOptimizer.get_instance(self.fdict) @@ -350,6 +562,15 @@ class FortranCodeGenerator(object): return calculation_code + export_code def visit_FluctuationField_code(self, field, alloc=None): + """Generates calculation code for turbulence fluctuations. + + Args: + field (FluctuationField): The fluctuation field. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: Renders 3D loop for calculating u' = u - . + """ field.array = alloc[field.name] if alloc else field.name rhs = ExpToCode(self.fdict).transform(field.field.exp) @@ -363,23 +584,74 @@ class FortranCodeGenerator(object): ) def visit_PrimaryField_code(self, field, alloc=None): + """No code generation needed for primary inputs. + + Args: + field (PrimaryField): The primary input. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: Comment statement. + """ return "! {} is read from file".format(field.name) def visit_PrimaryField_decl(self, field): + """No declaration needed for primary inputs. + + Args: + field (PrimaryField): The primary input. + + Returns: + str: Comment statement. + """ return "! {} is read from file".format(field.name) def visit_PrimaryField_alloc(self, field): + """No allocation needed for primary inputs. + + Args: + field (PrimaryField): The primary input. + + Returns: + str: Comment statement. + """ return "! {} is read from file".format(field.name) def visit_PrimaryField_free(self, field): + """No deallocation needed for primary inputs. + + Args: + field (PrimaryField): The primary input. + + Returns: + str: Comment statement. + """ return "! {} is read from file".format(field.name) def visit_DerivedField_code(self, field, alloc=None): + """Generates derivative calculation statements, calling Compact solver subroutines. + + Args: + field (DerivedField): The derivative field. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: Fortran subroutine call string. + """ field.array = alloc[field.name] if alloc else field.name varray = alloc[field.v] if alloc else field.v return "call {0} ( {2}, {1} )".format(field.op, varray, field.array) def visit_AveragedField_code(self, field, alloc=None): + """Generates local accumulation summation loop for spatial averaging. + + Args: + field (AveragedField): The averaged variable field. + alloc (dict, optional): Buffer allocation map. Defaults to None. + + Returns: + str: Renders local sum code block. + """ arrname = self.fdict[field.tgt].array + "(i,j,k)" if field.weighted is not None: arrname = arrname + " * " + field.w.array + "(i,j,k)" @@ -387,10 +659,29 @@ class FortranCodeGenerator(object): return Template(FortranTemplateStore.AVG_ARRAY_SUM).render(name=field.name, arrname=arrname) def visit_AveragedField_avg(self, field): + """Generates normalization and parallel reduction via MPI_ALLREDUCE. + + Args: + field (AveragedField): The averaged variable field. + + Returns: + str: Renders global reduce and normalize statement. + """ dWeight = (f"/ avg_{field.weighted}" if field.weighted else "") return Template(FortranTemplateStore.AVG_ARRAY_DIVIDE).render(name=field.name, dWeight=dWeight) def generate_write_avg(self, avglist): + """Generates final module output writers for 1D spatial averages. + + Also generates first and second derivatives of the averaged data using + ddx1d and d2dx1d subroutines. + + Args: + avglist (list): List of averaged field names. + + Returns: + str: Code block to write values to output files. + """ avgarr = "{}(i)" deriv1_avgarr = """call ddx1d ( xbuffer, {} ) ; write (200,*) xbuffer""" deriv2_avgarr = """call d2dx1d ( xbuffer, {} ) ; write (200,*) xbuffer""" @@ -411,46 +702,100 @@ class FortranCodeGenerator(object): @v_args(inline=True) class LarkToSympy(Transformer): - """Lark AST의 수학적 노드들을 SymPy 기호 수식 객체로 변환하는 Transformer 클래스입니다. + """Transformer to convert Lark AST math nodes to SymPy symbolic expressions. - 이를 통해 DSL 수식 트리를 SymPy 기호 객체로 매핑하고, SymPy의 강력한 수식 간소화(Simplify) - 및 공통 부분식 제거(CSE) 최적화 도구를 파이프라인 하부에서 활용할 수 있게 합니다. + Maps DSL expression trees into SymPy expressions to allow down-pipeline algebraic + simplification, common subexpression elimination (CSE), and memory optimization. """ + def __init__(self, fdict): - """Lark-to-SymPy Transformer를 초기화합니다. + """Initializes LarkToSympy transformer. Args: - fdict (dict): 변수명에서 물리 필드 정의 객체(FieldBase 자식 클래스들)로 매핑되는 딕셔너리. + fdict (dict): Dictionary mapping variable names to FieldBase objects. """ self.fdict = fdict def number(self, numeral): - # 상수를 SymPy Float 기호 객체로 변환 + """Converts number strings into SymPy Float objects. + + Args: + numeral (Token/str): Numeric string from the AST. + + Returns: + sympy.Float: SymPy floating point object. + """ return sympy.Float(float(numeral)) def env(self, name): - # 환경 변수($ 기호로 시작)를 SymPy Symbol로 매핑 + """Converts environment variable tokens to SymPy Symbol objects. + + Args: + name (Token): Environment variable name token (prefixed with $). + + Returns: + sympy.Symbol: SymPy symbol representation. + """ return sympy.Symbol(name.value) def paren(self, val): - # 괄호는 수식 우선순위 보존 후 내부 식 반환 + """Preserves precedence inside parentheses and returns the child expression. + + Args: + val (sympy.Expr): Expression inside parentheses. + + Returns: + sympy.Expr: The inner expression unchanged. + """ return val def var(self, name): - # 일반 변수명을 SymPy Symbol로 매핑 + """Maps variable name tokens to SymPy Symbol objects. + + Args: + name (Token): Variable name token. + + Returns: + sympy.Symbol: SymPy symbol representation. + """ return sympy.Symbol(name.value) def fluc(self, name): - # 변동량 u' 기호를 SymPy가 해석할 수 있도록 u__prime 기호명으로 매핑 + """Maps turbulence fluctuation variables (e.g., u') to a SymPy Symbol with '__prime' suffix. + + Args: + name (Token): Variable name token representing fluctuation. + + Returns: + sympy.Symbol: SymPy symbol with prime suffix identifier. + """ return sympy.Symbol(name.value + "__prime") def dnx(self, partial, b): - # ddx(u)와 같은 수치 미분항을 ddx_u 형태의 하나의 고유 Symbol로 묶어서 취급 + """Maps spatial derivative operations (e.g., ddx(u)) to a single composite SymPy Symbol. + + This treats the derivative term (e.g., ddx_u) as an independent symbol. + + Args: + partial (Token): Derivative operator token. + b (Token): Variable name token being differentiated. + + Returns: + sympy.Symbol: Composite derivative symbol representation. + """ signature = f"{partial.data}_{b.value}" return sympy.Symbol(signature) def icall(self, op, val): - # sqr, pow3 등의 인라인 전용 함수들을 SymPy 지수 표현식으로 다이렉트 변환 + """Converts inline functions (e.g., sqr, pow3) to direct SymPy exponent expressions. + + Args: + op (Token): Inline function operator token. + val (sympy.Expr): The function argument expression. + + Returns: + sympy.Expr: SymPy power expression. + """ if op.data == "sqr": return val**2 elif op.data == "pow3": @@ -458,7 +803,15 @@ class LarkToSympy(Transformer): return val def fcall(self, *args): - # 내장 수학 함수(sqrt, exp, log, abs, rxn_rate) 또는 사용자 정의 UDF들을 SymPy 함수 노드로 매핑 + """Maps standard built-in functions or UDFs to their SymPy representations. + + Args: + *args: Variable length argument list. The first argument is the function name Token, + and the subsequent arguments are the function parameter expressions. + + Returns: + sympy.Expr: SymPy function node or registered mathematical expression. + """ a = args[0] func_name = a.value if hasattr(a, 'value') else str(a) if func_name == "udf": @@ -466,21 +819,73 @@ class LarkToSympy(Transformer): return function_registry.to_sympy(func_name, *args[1:]) def neg(self, val): + """Converts negation to SymPy unary negation. + + Args: + val (sympy.Expr): The expression to negate. + + Returns: + sympy.Expr: Negated SymPy expression. + """ return -val def add(self, a, b): + """Converts addition to SymPy sum expression. + + Args: + a (sympy.Expr): Left expression. + b (sympy.Expr): Right expression. + + Returns: + sympy.Expr: SymPy addition expression. + """ return a + b def sub(self, a, b): + """Converts subtraction to SymPy difference expression. + + Args: + a (sympy.Expr): Left expression. + b (sympy.Expr): Right expression. + + Returns: + sympy.Expr: SymPy subtraction expression. + """ return a - b def mul(self, a, b): + """Converts multiplication to SymPy product expression. + + Args: + a (sympy.Expr): Left expression. + b (sympy.Expr): Right expression. + + Returns: + sympy.Expr: SymPy product expression. + """ return a * b def div(self, a, b): + """Converts division to SymPy division expression. + + Args: + a (sympy.Expr): Left expression. + b (sympy.Expr): Right expression. + + Returns: + sympy.Expr: SymPy division expression. + """ return a / b def udf(self, a): + """Maps user defined function names to string representations. + + Args: + a (Token): Function token. + + Returns: + str: Name of the user defined function. + """ return a.value log = lambda self: "log" @@ -491,19 +896,40 @@ class LarkToSympy(Transformer): class ArrayFCodePrinter(FCodePrinter): - """SymPy 식을 Fortran 코드로 인쇄할 때, 변수들을 적절한 다차원 격자점 인덱스 배열(예: var(i,j,k)) - 또는 평균 배열(예: var(i)) 형식으로 포맷팅하여 변환 출력해 주는 특수 Printer 클래스입니다. + """Custom SymPy printer that formats symbols as Fortran multidimensional array accesses. + + Transforms plain SymPy symbols into: + - 3D grid accesses (e.g., var(i,j,k)) for spatial fields. + - 1D array accesses (e.g., var(i)) for spatial averages. + - Subtraction expressions (e.g., (u(i,j,k) - avg_u(i))) for fluctuation variables. """ + def __init__(self, settings=None, array_symbols=None, avg_symbols=None): + """Initializes ArrayFCodePrinter with array settings and symbol catalogs. + + Args: + settings (dict, optional): Printer settings configuration. Defaults to None. + array_symbols (dict, optional): Maps 3D fields to their buffer array names. Defaults to None. + avg_symbols (dict, optional): Maps averaged fields to their 1D arrays. Defaults to None. + """ settings = settings or {} - settings.setdefault('source_format', 'free') # 기본 프리포맷 Fortran 95 준수 + settings.setdefault('source_format', 'free') # Default to free-form Fortran 95 settings.setdefault('standard', 95) super().__init__(settings) - self.array_symbols = array_symbols or {} # 3차원 필드 배열의 실제 물리 버퍼 명칭 매핑 - self.avg_symbols = avg_symbols or {} # 평균 필드 배열의 명칭 매핑 + self.array_symbols = array_symbols or {} + self.avg_symbols = avg_symbols or {} def _print_Float(self, expr): - # 배정밀도 실수 상수에 d0 접미사를 명시하여 컴파일러 정밀도 유실 방지 + """Prints Float constants with double precision (d0) suffix. + + Ensures precision is not degraded during Fortran compilation. + + Args: + expr (sympy.Float): SymPy float node. + + Returns: + str: Double precision literal string. + """ val = str(expr) if 'e' in val or 'E' in val: return val.replace('e', 'd').replace('E', 'd') @@ -512,14 +938,22 @@ class ArrayFCodePrinter(FCodePrinter): return val + "d0" def _print_Symbol(self, expr): + """Maps a SymPy Symbol to a Fortran array expression. + + Args: + expr (sympy.Symbol): SymPy Symbol to print. + + Returns: + str: Formatted Fortran variable access or inline fluctuation calculation. + """ name = expr.name - # 1. 3D 공간 격자점 버퍼 할당명에 매핑되어 있는 경우 (i,j,k) 차분 격자점 인덱스를 부착 + # 1. 3D grid array if name in self.array_symbols: return f"{self.array_symbols[name]}(i,j,k)" - # 2. X방향(i) 1차원 평균화 변수인 경우 (i) 인덱스 부착 + # 2. 1D averaged array if name in self.avg_symbols: return f"{self.avg_symbols[name]}(i)" - # 3. 변동량 변수(u__prime)인 경우, 원본 격자점 값과 1차원 평균값의 편차식인 '(u(i,j,k) - avg_u(i))'로 자동 인라인 전개 + # 3. Fluctuation substitution (e.g. u' -> u - ) if name.endswith("__prime"): base = name[:-7] arr = self.array_symbols.get(base, base) @@ -529,7 +963,16 @@ class ArrayFCodePrinter(FCodePrinter): return name def _print_Function(self, expr): - # SymPy 내장 함수가 아닌 사용자 정의 함수(rxn_rate, udf 등)의 정상적인 Fortran 출력을 위한 예외 폴백 처리 + """Prints custom non-standard functions securely in Fortran output. + + Fallback logic for rxn_rate, udf, etc. + + Args: + expr (sympy.Function): SymPy Function node. + + Returns: + str: Standard Fortran call syntax for the function. + """ try: return super()._print_Function(expr) except Exception: @@ -538,45 +981,65 @@ class ArrayFCodePrinter(FCodePrinter): class SympyOptimizer: - """컴파일러 내부에서 SymPy 수학 기호 연산 엔진을 제어하는 최적화 매니저 클래스입니다. - - 1. 수식 확장 대입(Substitution): 사용자가 정의한 물리 변수 중 디스크로 내보내지(export) 않는 - 임시 변수들의 수식을 참조하는 하부 수식에 재귀적으로 대입하여 거대한 하나의 식으로 확장합니다. - 2. 대수적 간소화(Simplify & Cancel): SymPy를 사용하여 분모 분자 소거 및 삼각/지수 대수 간소화를 적용합니다. - 3. 공통 부분식 제거(CSE - Common Subexpression Elimination): 복잡하게 얽힌 다차원 미분 수식 속에서 - 중복 계산되는 서브 식(예: 공통 항곱)을 탐지하여, 루프 외/내부에서 임시 스칼라 변수(x0, x1 등)에 선언한 후 - 루프 내 연산에서 한 번만 대입하여 연산하도록 루프 코드를 개조하여 FLOPS와 캐시 로드를 획기적으로 낮춥니다. + """Manages algebra optimization and Common Subexpression Elimination (CSE) via SymPy. + + This optimization engine: + 1. Expands intermediate temporary variables recursively. + 2. Simplifies arithmetic terms (fraction cancellation, trigonometric expansions). + 3. Runs CSE to pull duplicate sub-operations out into loop local scalar variables, + significantly reducing FLOPS and memory bandwidth requirements. """ _instance = None @classmethod def get_instance(cls, fdict): + """Retrieves or instantiates the singleton optimizer. + + Args: + fdict (dict): Current variable field registry mapping name -> FieldBase object. + + Returns: + SympyOptimizer: The active singleton instance. + """ if cls._instance is None or cls._instance.fdict is not fdict: cls._instance = cls(fdict) return cls._instance def __init__(self, fdict): + """Initializes SympyOptimizer registry caches. + + Args: + fdict (dict): Variable field registry. + """ self.fdict = fdict - self.sympy_cache = {} # 중복 연산 계산 방지를 위한 SymPy 식 캐시 + self.sympy_cache = {} self.exported_fields = set( name for name, f in fdict.items() if hasattr(f, 'attr') and f.attr.get('export') - ) # 디스크로 파일 출력이 설정된 최종 목표 변수 집합 + ) self.averaged_targets = set() self.avg_names = set() def set_averaged(self, averaged_dict): - # 평균화 대상이 되는 변수 집합을 수집 + """Sets the targets and names of averaged variables. + + Args: + averaged_dict (dict): Dictionary mapping average variable names to AveragedField objects. + """ self.averaged_targets = {a.target for a in averaged_dict.values()} self.avg_names = set(averaged_dict.keys()) def get_sympy_expr(self, name): - """특정 변수 명칭에 대해 기호적으로 완전히 확장 대입된 SymPy Expression 객체를 빌드하여 캐싱합니다. - - 기본 입력 변수(Primary), 미분 변수(Derived), 평균 변수(Averaged), 변동량 변수(Fluctuation)는 - 치환하지 않고 독립적인 리프 기호(Symbol)로 둡니다. 그 외 중간에서만 활용되는 일반 임시 Derived 변수들은 - 의존하는 원본 수식들을 탐색하여 재귀적으로 인라인 대체(expand) 시킴으로써, SymPy 기호 엔진이 - 루프 수식 전체의 논리적 최적화를 전역적으로 수행할 수 있도록 합니다. + """Recursively builds and caches a fully substituted SymPy Expression for a variable. + + Avoids expanding boundary fields (PrimaryField, DerivedField representing spatial derivatives, + AveragedField, and FluctuationField) to preserve the grid boundaries. Expands other temporary variables. + + Args: + name (str): Variable name. + + Returns: + sympy.Expr: SymPy node representing the fully-expanded mathematical expression. """ if name in self.sympy_cache: return self.sympy_cache[name] @@ -588,7 +1051,7 @@ class SympyOptimizer: self.sympy_cache[name] = expr return expr - if hasattr(field, 'op'): # DerivedField (ddx 등 미분 결과) + if hasattr(field, 'op'): # DerivedField (ddx, etc.) expr = sympy.Symbol(name) self.sympy_cache[name] = expr return expr @@ -606,7 +1069,7 @@ class SympyOptimizer: transformer = LarkToSympy(self.fdict) expr = transformer.transform(field.exp) - # 재귀적으로 의존 중인 캐싱되지 않은 중간 계산 임시 변수들을 수식 본문으로 확장 치환(Substitution) + # Recursively substitute intermediate variables expanded_expr = expr changed = True while changed: @@ -623,8 +1086,6 @@ class SympyOptimizer: is_exported = sym_name in self.exported_fields is_averaged_target = sym_name in self.averaged_targets - # 최종 출력 대상이거나 물리적 경계 처리가 필요한 핵심 변수가 아니라면, - # 기 기하학적 수식을 최적화하기 위해 현재 식 안으로 대입해 버립니다. if not (is_derived_field or is_averaged_field or is_primary_field or is_exported or is_averaged_target): sub_dict[sym] = self.get_sympy_expr(sym_name) changed = True @@ -636,8 +1097,15 @@ class SympyOptimizer: return expanded_expr def calculate_flops_and_heavy(self, expr): - """수식에서 수행되는 부동 소수점 연산(FLOPS) 및 계산 부하가 큰 연산(Division, Sqrt, Exp 등)의 개수를 평가합니다. - 이를 통해 최적화 리포트에 연산 비용 증감률을 리포팅합니다. + """Measures computational cost (FLOPS and heavy operators) for a SymPy expression. + + Useful for logging optimization statistics. + + Args: + expr (sympy.Expr): The expression to evaluate. + + Returns: + tuple: (flops_count, heavy_operators_count). """ flops = 0 heavy = 0 @@ -649,10 +1117,10 @@ class SympyOptimizer: elif isinstance(node, sympy.Pow): base, exp = node.args if exp == 0.5 or exp == -0.5: - flops += 10 # 제곱근 및 역제곱근은 대략 10 flops 가중치 부여 + flops += 10 # Square root/inv square root weight heavy += 1 elif exp == -1: - flops += 4 # 역수 나눗셈은 4 flops 가중치 부여 + flops += 4 # Division weight heavy += 1 elif isinstance(exp, sympy.Integer): val = abs(int(exp)) @@ -667,7 +1135,7 @@ class SympyOptimizer: flops += 10 heavy += 1 elif name in ('exp', 'log', 'sin', 'cos', 'tan', 'rxn_rate'): - flops += 10 # 특수 수학 함수 연산은 10 flops 가중치 및 Heavy 연산자로 평가 + flops += 10 heavy += 1 elif name == 'Abs': flops += 1 @@ -677,8 +1145,14 @@ class SympyOptimizer: return flops, heavy def count_3d_loads(self, expr, three_d_arrays): - """수식 내부에서 참조하는 3차원 격자점 배열의 총 메모리 로드 횟수를 카운트합니다. - 메모리 대역폭 한계(Memory-bound)에 부딪히는 HPC 연산의 병목을 평가하는 데 중요한 지표입니다. + """Counts the total 3D array memory read accesses in the expression. + + Args: + expr (sympy.Expr): The expression to analyze. + three_d_arrays (set): Names of active 3D arrays. + + Returns: + int: The memory load count. """ count = 0 for node in sympy.preorder_traversal(expr): @@ -687,16 +1161,19 @@ class SympyOptimizer: return count def optimize_field(self, name, alloc=None): - """개별 물리 필드 수식을 SymPy를 사용해 최적화하고 공통 부분식(CSE) 코드를 추출합니다. - + """Optimizes a physical field expression and extracts Common Subexpressions. + + Applies SymPy simplification and CSE, printing optimization reports to stderr. + Args: - name (str): 최적화할 필드 변수명. - alloc (dict): 변수명에서 버퍼 배열명(xyzbufferN)으로 매핑되는 테이블. - + name (str): The name of the field to optimize. + alloc (dict, optional): Buffer allocation mapping. Defaults to None. + Returns: - rhs (str): 최적화가 완료된 최종 Fortran 우변(Right-Hand Side) 코드 문자열. - cse_decls (list): CSE 추출 결과 생성된 임시 실수 변수 선언 리스트 (예: real(real64) :: x0). - cse_assigns (list): 루프 내부 대입에 사용될 임시 변수의 연산식 리스트. + tuple: (rhs_code, cse_declarations, cse_assignments) where: + rhs_code (str): The final right hand side Fortran expression. + cse_declarations (list of str): Code strings to declare local CSE scalars. + cse_assignments (list of str): Code strings to calculate CSE values. """ expr = self.get_sympy_expr(name) @@ -705,15 +1182,14 @@ class SympyOptimizer: if hasattr(v, 'dim') and v.dim == ':,:,:' } - # 최적화 전 비용 수집 + # Optimization metrics before before_flops, before_heavy = self.calculate_flops_and_heavy(expr) before_loads = self.count_3d_loads(expr, three_d_arrays) - # 1차 대수적 간소화 및 약분 소거 + # Simplify expression simplified_expr = sympy.simplify(expr) simplified_expr = sympy.cancel(simplified_expr) - # 기호명을 실제 Fortran 격자 배열명(xyzbufferN)으로 매핑하기 위한 준비 array_symbols = {} for k, v in self.fdict.items(): if hasattr(v, 'array') and v.array: @@ -726,12 +1202,11 @@ class SympyOptimizer: avg_symbols = {k: k for k in getattr(self, 'avg_names', [])} printer = ArrayFCodePrinter(array_symbols=array_symbols, avg_symbols=avg_symbols) - # 2차: 공통 부분식 제거(CSE) 알고리즘 가동 - # 복잡하게 중복 사용되는 수식 구조를 서브 트리로 분리하여 임시 기호(x0, x1 등)와 최종 축소 식으로 분할 + # Perform Common Subexpression Elimination replacements, reduced_exprs = sympy.cse(simplified_expr) reduced_expr = reduced_exprs[0] - # 최적화 후 비용 계산 + # Optimization metrics after after_flops = 0 after_heavy = 0 after_loads = 0 @@ -758,7 +1233,6 @@ class SympyOptimizer: 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: @@ -766,7 +1240,6 @@ class SympyOptimizer: else: est_speedup = "Minimal / Already optimal" - # 컴파일 중 최적화 상세 리포트를 stderr로 콘솔 화면에 로깅 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") @@ -776,12 +1249,9 @@ class SympyOptimizer: cse_decls = [] cse_assigns = [] - # CSE 추출 결과를 Fortran 코드로 포매팅 if replacements: for temp_var, temp_expr in replacements: - # 임시 변수 로컬 스칼라 선언 구문 작성 cse_decls.append(f"real(real64) :: {temp_var}") - # 루프 내에서 먼저 연산 후 할당하는 구문 작성 cse_assigns.append(f"{temp_var} = {printer.doprint(temp_expr)}") rhs = printer.doprint(reduced_expr) @@ -790,25 +1260,40 @@ class SympyOptimizer: class CollectDefinitions(Visitor): - """Lark AST의 전체 노드를 스캔하며 정의문(변수 리스트, 대입식, 평균화 조건)을 수집하여 - 컴파일러의 빌딩 블록 객체들로 구축하는 Visitor 클래스입니다 (Stage 1 핵심 작동부). + """Visitor that walks the Lark AST to collect variable and assignment declarations. + + Gathers the primary fields (direct file inputs), derived calculation definitions, + and average directives (statistical averages) to build the compiler's initial model. """ - def __init__ (self, primary, derived, averaged): - self.primary = primary # 기본 입력 물리 필드명 집합 (격자 파일 등에서 읽음) - self.derived = derived # 유도/계산식 필드 딕셔너리 - self.averaged = averaged # 평균 연산 조건 딕셔너리 + def __init__(self, primary, derived, averaged): + """Initializes CollectDefinitions visitor. + + Args: + primary (set): Set to accumulate primary input field names. + derived (dict): Dictionary to accumulate derived Field objects. + averaged (dict): Dictionary to accumulate average variable targets. + """ + self.primary = primary + self.derived = derived + self.averaged = averaged def varlist(self, tree): - # 괄호 안 [u, v, w] 같은 형식으로 표기된 기본(Primary) 변수 리스트 파싱 + """Parses list of primary inputs declared in bracket syntax (e.g. [u, v, w]). + + Args: + tree (Tree): Lark AST node for varlist. + """ for v in tree.children: self.primary.add(v.value) - # 의존성 테이블에 기본 변수 객체 등록 self.derived[v.value] = PrimaryField(v.value, self.derived) - def assign_var (self, tree): - # 등호(=) 연산자를 통한 변수 계산식 정의 노드 파싱 - # 속성값(예: (export=true, latex="..."))이 존재하면 추출 + def assign_var(self, tree): + """Parses variable assignment statements and registers corresponding Field objects. + + Args: + tree (Tree): Lark AST node for assignment. + """ if len(tree.children) > 2: lval, lattr, rval = tree.children else: @@ -824,11 +1309,14 @@ class CollectDefinitions(Visitor): if lval.value in self.derived: raise ValueError("duplicate definition of " + lval) - # Field 객체를 생성하여 derived 계산 테이블에 등록 self.derived[lval.value] = Field(lval.value, attr_dict, rval, self.derived) - def assign_avg_var (self, tree): - # avg 또는 avg$w {u, v, ...} 와 같은 평균화 지시어 파싱 및 등록 + def assign_avg_var(self, tree): + """Parses average directives and registers target variables for spatial averaging. + + Args: + tree (Tree): Lark AST node for average declaration. + """ w = tree.children[0] targets = tree.children[1:] @@ -839,47 +1327,99 @@ class CollectDefinitions(Visitor): class ExpInspector(Visitor): - """수식 AST 노드들을 순회하며 해당 식 내부의 변동량(Fluctuation) 포함 여부, - 참조 중인 다른 물리 변수 종속성 목록(dep), 그리고 수치 미분항(deriv) 목록을 추출하는 Visitor 클래스입니다. + """Visitor to inspect mathematical AST nodes to extract dependencies and attributes. + + Finds the list of dependent variables, checks if the expression references + turbulence fluctuation variables (primed variables), and extracts derivative operators + such as spatial first and second order derivatives (e.g. ddx, d2dy). """ + def __init__(self): + """Initializes ExpInspector state.""" self.fluctuation = False self.dep = set([]) self.deriv = set([]) @classmethod def inspect(cls, tree): + """Inspects the given AST tree and returns extracted attributes. + + Args: + tree (Tree): Lark AST subtree. + + Returns: + tuple: (has_fluctuation, dependencies_set, derivatives_set). + """ self = cls() return self(tree) def __call__(self, tree): + """Executes the inspection traversal. + + Args: + tree (Tree): Lark AST subtree. + + Returns: + tuple: (has_fluctuation, dependencies_set, derivatives_set). + """ self.visit(tree) return self.fluctuation, self.dep, self.deriv def fluc(self, tree): - # 식 내부에 변동량 항이 존재함을 기록하고 종속성에 등록 + """Processes a fluctuation node. + + Args: + tree (Tree): Fluctuation AST node. + """ self.fluctuation = True self.dep.add(tree.children[0].value) def var(self, tree): + """Processes a variable reference node. + + Args: + tree (Tree): Variable reference AST node. + """ self.dep.add(tree.children[0].value) - def dnx (self, tree): - # ddx(u) 와 같이 수치 미분 노드를 만나면 ddx_u 형태의 중간 미분 변수를 종속성 리스트에 기록 + def dnx(self, tree): + """Processes a spatial derivative node, registering intermediate fields. + + Args: + tree (Tree): Derivative operation AST node. + """ op, v = tree.children deriv = "{}_{}".format(op.data, v.value) self.dep.add(deriv) self.deriv.add((op.data, v.value)) -@v_args(inline=True) # 메서드의 서명 형식을 자식 노드 매개변수 나열형으로 변환 +@v_args(inline=True) class ExpToLatex(Transformer): - """DSL 수학 표현식 AST를 문서를 검토하기 편하게 LaTeX 문법 수식으로 번역하는 번역기입니다.""" + """Transformer to compile the DSL mathematical AST into a LaTeX math string. + + Converts operations, variables, derivatives, and functions into LaTeX syntax + (e.g., partial derivative notation, fraction blocks, and bracket sizing) + to generate mathematical report files. + """ def __init__(self, fdict): + """Initializes ExpToLatex transformer. + + Args: + fdict (dict): Dictionary mapping variable names to FieldBase objects. + """ self.fdict = fdict def arithmatic_rooted(self, name): + """Checks if a variable's root operation is arithmetic. + + Args: + name (str): Variable name. + + Returns: + bool: True if arithmetic-rooted. + """ try: exproot = self.fdict[name].exp.data except AttributeError: @@ -889,6 +1429,14 @@ class ExpToLatex(Transformer): (exproot == "mul") or (exproot == "div")) def parenthise(self, name): + """Formats a variable name, wrapping it in parentheses if it has lower priority operators. + + Args: + name (str): Variable name. + + Returns: + str: Parenthesized LaTeX equation representation. + """ try: latex = self.fdict[name].latex latex_given = self.fdict[name].latex_given @@ -903,22 +1451,70 @@ class ExpToLatex(Transformer): return latex def number(self, numeral): + """Returns LaTeX literal for numbers. + + Args: + numeral (Token): Number token. + + Returns: + str: LaTeX representation. + """ return numeral def env(self, name): + """Returns LaTeX literal for environment variables. + + Args: + name (Token): Environment variable name token. + + Returns: + str: LaTeX representation. + """ return r"\mathrm{{{}}}".format(name.value) def paren(self, name): + """Formats parentheses. + + Args: + name (str): Inner LaTeX content. + + Returns: + str: Parenthesized string. + """ return "({})".format(str(name)) def var(self, name): + """Formats variables in LaTeX. + + Args: + name (Token): Variable token. + + Returns: + str: LaTeX variable representation. + """ return self.parenthise(name.value) def fluc(self, name): - # LaTeX 수식 표기용 u'' 프라임 기호 렌더링 - return self.parenthise(name.value)+ "''" + """Formats fluctuation prime (u'') variables in LaTeX. - def dnx (self, partial, b): + Args: + name (Token): Variable token. + + Returns: + str: LaTeX prime variable string. + """ + return self.parenthise(name.value) + "''" + + def dnx(self, partial, b): + """Formats spatial derivatives in LaTeX (e.g. \\partial_x(u)). + + Args: + partial (Token): Derivative operator token. + b (Token): Variable token. + + Returns: + str: LaTeX partial derivative expression. + """ fmt = r"\partial_{{{}}}" coord = partial.data[-1] op = fmt.format(coord + coord if len(partial.data) > 3 else coord) @@ -933,7 +1529,16 @@ class ExpToLatex(Transformer): return eq - def icall (self, a, b): + def icall(self, a, b): + """Formats inline functions (sqr, pow3) in LaTeX. + + Args: + a (Token): Inline function operator. + b (str): LaTeX representation of argument. + + Returns: + str: LaTeX representation. + """ if a.data == "sqr": fcode = "({0})^2".format(b) elif a.data == "pow3": @@ -942,60 +1547,149 @@ class ExpToLatex(Transformer): fcode = "({0})".format(b) return fcode - def fcall (self, *args): + def fcall(self, *args): + """Formats function calls in LaTeX. + + Args: + *args: Variable length arguments. The first argument is function name token. + + Returns: + str: LaTeX function representation. + """ a = args[0] func_name = a.value if hasattr(a, 'value') else str(a) return function_registry.to_latex(func_name, *args[1:]) def neg(self, b): + """Formats negation. + + Args: + b (str): Inner LaTeX expression. + + Returns: + str: LaTeX negated expression. + """ fcode = "(-{})".format(b) return fcode def add(self, a, b): + """Formats addition. + + Args: + a (str): Left operand. + b (str): Right operand. + + Returns: + str: LaTeX addition expression. + """ fcode = "{} + {}".format(a, b) return fcode def sub(self, a, b): + """Formats subtraction. + + Args: + a (str): Left operand. + b (str): Right operand. + + Returns: + str: LaTeX subtraction expression. + """ fcode = "{} - {}".format(a, b) return fcode def mul(self, a, b): + """Formats multiplication. + + Args: + a (str): Left operand. + b (str): Right operand. + + Returns: + str: LaTeX multiplication expression. + """ fcode = "{} {}".format(a, b) return fcode def div(self, a, b): + """Formats division. + + Args: + a (str): Left operand. + b (str): Right operand. + + Returns: + str: LaTeX division expression. + """ fcode = "{} / {}".format(a, b) return fcode - log = lambda self : "\log" - exp = lambda self : "\exp" - sqrt = lambda self : "sqrt" - abs = lambda self : "abs" - rxn_rate = lambda self : "\omega" - udf = lambda self, a : a.value - - + log = lambda self: "\log" + exp = lambda self: "\exp" + sqrt = lambda self: "sqrt" + abs = lambda self: "abs" + rxn_rate = lambda self: "\omega" + udf = lambda self, a: a.value @v_args(inline=True) class ExpToCode(Transformer): - """DSL 수학 수식 트리를 Fortran의 3차원 인덱스 코드 형태의 수식 문자열로 다이렉트 매핑하는 번역기입니다. - SymPy 최적화를 사용하지 않는 대체 연산 경로 등에서 보조적으로 사용됩니다. + """Transformer to compile the DSL mathematical AST directly into Fortran array expressions. + + Formats variable accesses with spatial index wrappers (e.g., `(i,j,k)` or `(i)`) and + converts inline functions or standard math calls. """ def __init__(self, fdict): + """Initializes ExpToCode transformer. + + Args: + fdict (dict): Dictionary mapping variable names to FieldBase objects. + """ self.fdict = fdict def number(self, numeral): + """Converts float literal numbers. + + Args: + numeral (Token): Number token. + + Returns: + str: Floating point string. + """ return str(float(numeral)) def env(self, name): + """Converts environment variables. + + Args: + name (Token): Environment variable name token. + + Returns: + str: Variable name. + """ return name.value def paren(self, name): + """Wraps in parentheses. + + Args: + name (str): Inner expression. + + Returns: + str: Parenthesized string. + """ return "({})".format(str(name)) def var(self, name): + """Formats a variable access with spatial grid index (i,j,k). + + Args: + name (Token): Variable token. + + Returns: + str: Fortran array access. + """ try: arrname = self.fdict[name.value].array except KeyError: @@ -1004,6 +1698,16 @@ class ExpToCode(Transformer): return arrname + "(i,j,k)" def fluc(self, name): + """Formats a fluctuation prime variable access. + + Saves index reference for inline replacement of averages. + + Args: + name (Token): Fluctuation variable token. + + Returns: + str: Fortran inline fluctuation subtraction formula. + """ try: arrname = self.fdict[name.value].array except KeyError: @@ -1012,7 +1716,16 @@ class ExpToCode(Transformer): fmt = "({0}(i,j,k) - {{0}}avg_{1}(i))" return fmt.format(arrname, name.value) - def dnx (self, partial, b): + def dnx(self, partial, b): + """Formats spatial derivatives as array accesses. + + Args: + partial (Token): Derivative operator. + b (Token): Variable token. + + Returns: + str: Fortran array access for derivative. + """ signature = "{}_{}".format(partial.data, b.value) try: arrname = self.fdict[signature].array @@ -1021,7 +1734,16 @@ class ExpToCode(Transformer): return arrname + "(i,j,k)" - def icall (self, a, b): + def icall(self, a, b): + """Formats inline functions (sqr, pow3) to Fortran multiplication. + + Args: + a (Token): Inline function operator. + b (str): Argument code. + + Returns: + str: Fortran math expression. + """ if a.data == "sqr": fcode = "(({0})*({0}))".format(b) elif a.data == "pow3": @@ -1030,44 +1752,103 @@ class ExpToCode(Transformer): fcode = "({0})".format(b) return fcode - def fcall (self, *args): + def fcall(self, *args): + """Formats standard math function calls. + + Args: + *args: Variable length argument list. First argument is the function token. + + Returns: + str: Fortran function call string. + """ a = args[0] b = ", ".join(args[1:]) fcode = "( {} ( {} ) )".format(a, b) return fcode def neg(self, b): + """Formats negation. + + Args: + b (str): Code string to negate. + + Returns: + str: Negated code string. + """ fcode = "( - {} )".format(b) return fcode def add(self, a, b): + """Formats addition. + + Args: + a (str): Left operand code. + b (str): Right operand code. + + Returns: + str: Fortran addition code. + """ fcode = "( {} + {} )".format(a, b) return fcode def sub(self, a, b): + """Formats subtraction. + + Args: + a (str): Left operand code. + b (str): Right operand code. + + Returns: + str: Fortran subtraction code. + """ fcode = "( {} - {} )".format(a, b) return fcode def mul(self, a, b): + """Formats multiplication. + + Args: + a (str): Left operand code. + b (str): Right operand code. + + Returns: + str: Fortran multiplication code. + """ fcode = "( {} * {} )".format(a, b) return fcode def div(self, a, b): + """Formats division. + + Args: + a (str): Left operand code. + b (str): Right operand code. + + Returns: + str: Fortran division code. + """ fcode = "( {} / {} )".format(a, b) return fcode - log = lambda self : "log" - exp = lambda self : "exp" - sqrt = lambda self : "sqrt" - abs = lambda self : "abs" - rxn_rate = lambda self : "rxn_rate" - udf = lambda self, a : a.value - - + log = lambda self: "log" + exp = lambda self: "exp" + sqrt = lambda self: "sqrt" + abs = lambda self: "abs" + rxn_rate = lambda self: "rxn_rate" + udf = lambda self, a: a.value def make_allocate(name, shape, init_zero=True): - """Fortran 배열을 동적 할당하고 예외 발생 시 프로세스를 안전하게 폭파시키는 할당 코드를 작성해 줍니다.""" + """Fortran 배열을 동적 할당하고 예외 발생 시 프로세스를 안전하게 폭파시키는 할당 코드를 작성해 줍니다. + + Args: + name (str): 할당할 배열의 이름. + shape (str): 할당 크기 형태 (예: 'nxp,nyp,nzp'). + init_zero (bool, optional): True인 경우 0.0d0으로 초기화 구문을 덧붙입니다. Defaults to True. + + Returns: + str: Fortran dynamic allocation code block. + """ alloc_str = f"allocate({name}({shape}), stat=ierr)\n" alloc_str += f"if (ierr /= 0) then\n" alloc_str += f" write(0,*) 'Error: allocation of {name} failed on process', myid\n" @@ -1079,22 +1860,48 @@ def make_allocate(name, shape, init_zero=True): class DependencyNode(object): - """의존성 관계 분석을 담당하는 추상 인터페이스 및 노드 클래스입니다.""" + """의존성 관계 분석을 담당하는 추상 인터페이스 및 노드 클래스입니다. + + Compiler pipeline stages use this graph to resolve topological sort order. + """ + def __init__(self, name, fdict): + """Initializes DependencyNode. + + Args: + name (str): Variable name. + fdict (dict): Global variable registry dictionary. + """ self.name = name self.fdict = fdict - self.dep = set([]) # 종속된(의존 중인) 하위 필드 리스트 - self.fluc = False # 변동량 편차 계산 대상인지 여부 + self.dep = set([]) + self.fluc = False def depends_on(self, a): + """Checks if this node depends directly on the given variable. + + Args: + a (str): Target variable name. + + Returns: + bool: True if directly dependent. + """ return a in self.dep def is_fluctuation(self): + """Checks if this node is a fluctuation variable. + + Returns: + bool: True if fluctuation variable. + """ return self.fluc def checkFluctuation(self): """본 변수 혹은 의존하고 있는 하위 기호들 중에 변동량(Fluctuation) 관련 계산이 개입되어 있는지 상향식으로 전파 추적하는 재귀 메서드입니다. + + Returns: + set: Set of variable names that require fluctuation calculations. """ fset = set([]) for d in map(self.fdict.get, self.dep): @@ -1106,6 +1913,9 @@ class DependencyNode(object): def depClosure(self): """해당 변수를 계산하기 위해 선행 계산되어야 하는 모든 하위 변수 노드들을 재귀적으로 타고 내려가 총합 폐쇄 집합(Closure Set)으로 묶어 반환합니다. + + Returns: + set: Complete transitive dependency set. """ fset = set(self.dep) for d in self.dep: @@ -1113,23 +1923,36 @@ class DependencyNode(object): return fset -class FieldBase (DependencyNode): +class FieldBase(DependencyNode): """모든 물리 필드 객체의 최상위 기본 클래스로서 공통 데이터 구조를 정의합니다.""" - def __init__ (self, name, fdict): + + def __init__(self, name, fdict): + """Initializes FieldBase properties. + + Args: + name (str): Variable name. + fdict (dict): Global variable registry dictionary. + """ super(FieldBase, self).__init__(name, fdict) - self.array = name # 실제 메모리 버퍼 상의 지칭 배열 명칭 - self.prime = False # 기본 데이터 파일로부터 읽어 들이는 원본 필드인지 여부 - self.shape = "nxp,nyp,nzp" # 3차원 그리드 구조 + self.array = name + self.prime = False + self.shape = "nxp,nyp,nzp" self.dim = ":,:,:" - def export_on (self): + def export_on(self): + """Checks if disk exporting is enabled for this field. + + Returns: + bool: Always False for the base field class. + """ return False - def __repr__ (self): + def __repr__(self): + """String representation of the field (returns variable name).""" return self.name -class FieldExporter (object): +class FieldExporter(object): """물리 필드 데이터를 병렬 분산 디스크 시스템으로 직접 추출(Export)하는 고성능 MPI-IO 서브루틴 블록을 정의하는 도메인 데이터 클래스입니다. """ @@ -1138,7 +1961,14 @@ class FieldExporter (object): integer(kind=MPI_OFFSET_KIND) :: offset """ - def __init__ (self, name, attr, parent): + def __init__(self, name, attr, parent): + """Initializes FieldExporter with MPI configurations. + + Args: + name (str): Field exporter name. + attr (dict): Attributes dictionary containing slice coordinates (e.g. xs, xe). + parent (Field): Parent field object owning this exporter. + """ self.name = name self.attr = attr self.parent = parent @@ -1179,13 +2009,21 @@ end do self.use_subarray = ("xpts" not in self.params) -class Field (FieldBase): +class Field(FieldBase): """일반 대입 계산 변수를 관리하며, 3차원 격자점 루프 코드 생성을 담당하는 핵심 클래스입니다. SymPy 최적화 및 CSE 적용 코드를 루프 본문에 결합합니다. """ - def __init__ (self, name, attr, exp, fdict): - super(Field,self).__init__(name, fdict) + def __init__(self, name, attr, exp, fdict): + """Initializes Field properties and inspects expression details. + + Args: + name (str): Calculated variable name. + attr (dict): Attribute configuration tags. + exp (Tree): Mathematical equation AST subtree. + fdict (dict): Global variable registry dictionary. + """ + super(Field, self).__init__(name, fdict) self.attr = attr self.exp = exp self.fluc, self.dep, self.derivs = ExpInspector.inspect(exp) @@ -1204,18 +2042,30 @@ class Field (FieldBase): except KeyError: pass - def export_on (self): + def export_on(self): + """Checks if file exporting is enabled. + + Returns: + bool: True if an exporter is configured. + """ return self.exporter is not None -class FluctuationField (FieldBase): +class FluctuationField(FieldBase): """물리 필드의 난류 변동 성분(Fluctuation, u' = u - )을 계산하기 위한 변수 클래스입니다. 수식 내의 u' 기호를 평균량과의 차이 수식으로 팽창하여 할당합니다. """ - def __init__ (self, w, field, fset, fdict): + def __init__(self, w, field, fset, fdict): + """Initializes FluctuationField. - super(FluctuationField,self).__init__(self.id(w,field), fdict) + Args: + w (str/None): Weight variable name or average identifier. + field (str): Base variable name (e.g. 'u'). + fset (set): Active fluctuation dependency names. + fdict (dict): Global variable registry. + """ + super(FluctuationField, self).__init__(self.id(w, field), fdict) if w is not None: self.w = w + "_" @@ -1225,7 +2075,7 @@ class FluctuationField (FieldBase): self.field = fdict[field] self.dep = self.field.dep - fset for df in self.field.dep & fset: - self.dep.add(self.id(w,df)) + self.dep.add(self.id(w, df)) self.comment = ExpToCode(self.fdict).transform(self.field.exp) @@ -1235,7 +2085,16 @@ class FluctuationField (FieldBase): self.comment = self.name + " = " + self.comment @classmethod - def id (cls, w, field): + def id(cls, w, field): + """Generates dynamic fluctuation field naming key. + + Args: + w (str/None): Average weight suffix. + field (str): Base field name. + + Returns: + str: Generated composite fluctuation variable name. + """ if w: name = "{}____{}_avg".format(field, w) else: @@ -1243,29 +2102,42 @@ class FluctuationField (FieldBase): return name -class PrimaryField (FieldBase): +class PrimaryField(FieldBase): """격자 정보나 외부 물리계 수치 데이터(u, v, w, T 등) 파일에서 사전에 로드하여 메모리에 상주하는 기본 원본 입력 필드 클래스입니다. 자체 계산 루프나 동적 할당 코드를 직접 생성하지 않습니다. """ - def __init__ (self, name, fdict): - super(PrimaryField,self).__init__(name, fdict) + def __init__(self, name, fdict): + """Initializes PrimaryField. + + Args: + name (str): Input field name. + fdict (dict): Variable registry dictionary. + """ + super(PrimaryField, self).__init__(name, fdict) self.derivs = set([]) self.prime = True self.latex = name self.latex_given = None -class DerivedField (FieldBase): +class DerivedField(FieldBase): """수치 공간 미분(ddx, d2dy 등)을 수행하여 계산되는 유도 필드 클래스입니다. Fortran 수치 차분 패키지 서브루틴(Compact.f90 에 구현된 dfnonp, dfp 등)의 동적 호출 코드를 출력합니다. """ - def __init__ (self, op, v, fdict): + def __init__(self, op, v, fdict): + """Initializes DerivedField derivative node. + + Args: + op (str): Differential operator name (e.g. 'ddx'). + v (str): Variable being differentiated (e.g. 'u'). + fdict (dict): Global variable registry dictionary. + """ name = "{}_{}".format(op, v) - super(DerivedField,self).__init__(name, fdict) + super(DerivedField, self).__init__(name, fdict) self.op = op self.v = v self.dep = set([v]) @@ -1274,24 +2146,38 @@ class DerivedField (FieldBase): self.latex = partial + "(" + fdict[v].latex + ")" -class AveragedField (FieldBase): +class AveragedField(FieldBase): """특정 물리 필드를 격자의 동질 차원(예: X 방향 1D선상 평균)에 대해 공간 통계 평균(Average) 연산을 수행하는 1차원 필드 클래스입니다. """ @classmethod - def id (cls, w, tgt): + def id(cls, w, tgt): + """Generates average field naming key. + + Args: + w (str/None): Weight parameter. + tgt (str): Target field variable name. + + Returns: + str: Generated averaged variable key name. + """ if w: return "{}_avg_{}".format(w, tgt) else: return "avg_{}".format(tgt) + def __init__(self, w, tgt, fdict): + """Initializes AveragedField and populates dependency closure. - def __init__ (self, w, tgt, fdict): - - name = self.id(w,tgt) - super(AveragedField,self).__init__(name, fdict) - self.shape = "nxp" # Y, Z 차원을 평균화하여 날려버리므로 X방향 크기인 nxp 1차원 배열이 됨 + Args: + w (str/None): Average weights. + tgt (str): Target variable to average. + fdict (dict): Global variable registry dictionary. + """ + name = self.id(w, tgt) + super(AveragedField, self).__init__(name, fdict) + self.shape = "nxp" # Y, Z dimensions averaged out, leaving X-dimension array of size nxp self.dim = ":" self.target = tgt @@ -1304,7 +2190,7 @@ class AveragedField (FieldBase): self.tgt = tgt self.dep.add(tgt) else: - ftgt = FluctuationField.id(w,tgt) + ftgt = FluctuationField.id(w, tgt) self.tgt = ftgt self.dep.add(ftgt) @@ -1314,20 +2200,50 @@ class AveragedField (FieldBase): self.dep.add(w) self.latex += ("_{{{}}}".format(w)) - def isWeighted (self): + def isWeighted(self): + """Checks if average has a weighted density variable. + + Returns: + bool: True if weighted. + """ return self.weighted is not None - def pass1 (self): + def pass1(self): + """Checks if average variable can be computed in loop Pass 1. + + Returns: + bool: True if Pass 1 average. + """ return not self.pass2() - def pass2 (self): - # 수식 내에 u' 등 변동량 항이 포함되어 있으면, - # 원본 u의 평균치() 계산이 완료된 '이후'에만 연산이 가능하므로 Pass 2 대상이 됨 + def pass2(self): + """Checks if average variable requires Pass 2 (dependent on fluctuation). + + Returns: + bool: True if Pass 2 average. + """ return len(self.fset) > 0 class CompilationContext(object): + """Holds compilation pipeline state across parsing, resolution, and optimization stages. + + Attributes: + primary (set of str): Names of primary input fields. + derived (dict of str -> FieldBase): Calculated, derivative, and fluctuation fields. + averaged (dict of str -> AveragedField): Spatial averaged variables. + dependency (dict of str -> set of str): DAG representing direct dependencies. + pass1 (list of str): Topologically sorted variables for average-precursor calculation. + pass2 (list of str): Topologically sorted variables for post-average calculations. + avg1 (set of AveragedField): Averaged variables calculated in Pass 1. + avg2 (set of AveragedField): Averaged variables calculated in Pass 2. + alloc1 (dict of str -> str): Buffer pooling maps for Pass 1. + alloc2 (dict of str -> str): Buffer pooling maps for Pass 2. + narr (int): Maximum number of shared XYZ buffer arrays needed. + """ + def __init__(self): + """Initializes an empty CompilationContext.""" self.primary = set() self.derived = {} self.averaged = {} @@ -1421,7 +2337,14 @@ def tok_to_str(tok): class ParserStage(object): + """Compiler pipeline Stage 1: Parses DSL specifications and extracts initial definitions. + + Reads raw DSL specifications and uses Lark parser to build the AST. + Populates primary inputs, derived variables, and averaged variable lists. + """ + def __init__(self): + """Initializes ParserStage with calc_grammar.""" self.parser = Lark(calc_grammar, parser='lalr', lexer_callbacks={ @@ -1431,12 +2354,30 @@ class ParserStage(object): }) def execute(self, terms_raw, ctx): + """Executes Stage 1 parser. + + Args: + terms_raw (str): Raw DSL term specification string. + ctx (CompilationContext): Active compilation context. + """ tree = self.parser.parse(terms_raw) CollectDefinitions(ctx.primary, ctx.derived, ctx.averaged).visit(tree) class DerivativeExpansionStage(object): + """Compiler pipeline Stage 2: Expands differential operators and fluctuation terms. + + Finds derivative expressions (e.g., ddx, d2dy) and fluctuation identifiers (u'), + instantiates DerivedField and FluctuationField objects, registers them in + the variable registry, and builds initial DAG dependencies. + """ + def execute(self, ctx): + """Executes Stage 2 derivative and fluctuation expansion. + + Args: + ctx (CompilationContext): Active compilation context. + """ # 1. 계산식 내에 존재하는 고차 차분 미분항(ddx, d2dy 등)을 찾아 중간 DerivedField로 등록 dset = set() for k, v in ctx.derived.items(): @@ -1468,7 +2409,20 @@ class DerivativeExpansionStage(object): class DependencyResolutionStage(object): + """Compiler pipeline Stage 3: Resolves data dependencies and orders calculations. + + Splits loops into: + - Pass 1 (calculating variables required prior to averages). + - Pass 2 (calculating fluctuations and statistics after averages are resolved). + Performs topological sorting to ensure correctness. + """ + def execute(self, ctx): + """Executes Stage 3 dependency resolution and topological sort. + + Args: + ctx (CompilationContext): Active compilation context. + """ # 이름 충돌 방지 검증 assert set(ctx.derived.keys()).isdisjoint(ctx.averaged.keys()) @@ -1488,7 +2442,17 @@ class DependencyResolutionStage(object): pass2calc.update(x.depClosure()) ctx.pass2 = self.sort_vars_new(ctx.dependency, pass2calc - ctx.primary) - def calc_size (self, dependency, ordered, remaining): + def calc_size(self, dependency, ordered, remaining): + """Calculates topological dependency metrics for ordering weight sorting. + + Args: + dependency (dict): Graph mapping variable names to dependency sets. + ordered (set): Topologically ordered variable names. + remaining (set): Unsorted remaining variable names. + + Returns: + int: The degree of active connection impact. + """ count = 0 dep_union = set() for v in remaining: @@ -1498,7 +2462,18 @@ class DependencyResolutionStage(object): count += 1 return count - def sort_vars_new (self, dependency, group): + def sort_vars_new(self, dependency, group): + """Performs topological sort using an impact-weight heuristic. + + Minimizes variable lifetime durations to optimize array reuse. + + Args: + dependency (dict): Graph mapping variable names to dependency sets. + group (set): Set of variable names to sort. + + Returns: + list: Topologically sorted list of variable names. + """ order = [] remain = list(group) remain.sort() @@ -1523,7 +2498,21 @@ class DependencyResolutionStage(object): class SympyOptimizationStage(object): + """Compiler pipeline Stage 4: Optimizes mathematical equations and shares memory buffers. + + This stage: + 1. Invokes the SympyOptimizer to apply algebraic simplification and CSE. + 2. Updates variable dependencies to reflect optimized equations. + 3. Runs liveness window analysis to map multiple non-overlapping temporary variables + to a limited set of shared XYZ buffers (array pooling) to prevent RAM exhaustion. + """ + def execute(self, ctx): + """Executes Stage 4 mathematical optimization and array buffer sharing. + + Args: + ctx (CompilationContext): Active compilation context. + """ # 1. SymPy 수식 최적화 엔진 초기화 opt = SympyOptimizer.get_instance(ctx.derived) opt.set_averaged(ctx.averaged) @@ -1542,7 +2531,7 @@ class SympyOptimizationStage(object): updated_dependency[name] = dep_set ctx.dependency = updated_dependency - self.array_name = "xyzbuffer{}" # Pooling용 공용 3차원 버퍼 이름 포맷 + self.array_name = "xyzbuffer{}" # 3. Pass 1 및 Pass 2 연산 순서 배열들에 대해 각각 버퍼 공유 매핑(Pooling) 수행 narr1, alloc1 = (self.allocate_arr(ctx, ctx.pass1)) @@ -1551,10 +2540,23 @@ class SympyOptimizationStage(object): # 전체 프로그램에서 필요한 동적 공유 3D 버퍼 배열의 최대 크기 설정 ctx.narr = max(narr1, narr2) - ctx.alloc1 = alloc1 # Pass 1 변수명 -> 공용 xyzbuffer 명칭 매핑 정보 - ctx.alloc2 = alloc2 # Pass 2 변수명 -> 공용 xyzbuffer 명칭 매핑 정보 + ctx.alloc1 = alloc1 + ctx.alloc2 = alloc2 - def liveness (self, ctx, l1, g): + def liveness(self, ctx, l1, g): + """Analyzes variable lifetimes to identify overlap intervals. + + Constructs a liveness matrix where matrix[i, j] is True if variable i + is still live (in memory) at step j. + + Args: + ctx (CompilationContext): Active compilation context. + l1 (list): Topologically sorted variable names. + g (dict): Graph mapping variable names to dependency sets. + + Returns: + np.ndarray: Boolean liveness matrix of shape (len(l1), len(l1)). + """ import numpy as np img = np.zeros((len(l1), len(l1))) for i, v in enumerate(l1): @@ -1562,7 +2564,21 @@ class SympyOptimizationStage(object): img[i,i:j+1] = img[i,i:j+1] + (1 if v in g[l1[j]] else 0) return img > 0 - def allocate_arr (self, ctx, l): + def allocate_arr(self, ctx, l): + """Performs liveness-based memory buffer pooling. + + Assigns variables with disjoint active lifetimes to share the same + allocated `xyzbufferN` 3D arrays. + + Args: + ctx (CompilationContext): Active compilation context. + l (list): Topologically sorted list of variable names. + + Returns: + tuple: (max_buffers_needed, var_to_buffer_mapping) where: + max_buffers_needed (int): Maximum buffers required concurrently. + var_to_buffer_mapping (dict): Map of variable names to their pooled buffer arrays. + """ import numpy as np dg = ctx.dependency mask = self.liveness(ctx, l, dg) @@ -1584,7 +2600,18 @@ class SympyOptimizationStage(object): class FortranProgramWriter(object): + """Renders the final, compiled Fortran 95 post-processing modules. + + Uses Jinja2 templates to combine calculations, averages, declarations, + reductions, subarray parallel IO writers, and pooled dynamic array buffers. + """ + def write(self, ctx): + """Generates the code module and prints it directly to standard output. + + Args: + ctx (CompilationContext): The compiled context containing sorted equations and pooling allocations. + """ from resources.m_template import mod_form allvar = dict(ctx.derived) @@ -1643,6 +2670,14 @@ class FortranProgramWriter(object): print(Template(mod_form).render(**md)) def work_array_codes(self, narr): + """Generates dynamic memory helper statements for shared xyzbuffer buffers. + + Args: + narr (int): Number of buffers needed. + + Returns: + tuple: (declarations_string, allocations_string, deallocations_string). + """ array_name = "xyzbuffer{}" array_names = [array_name.format(i) for i in range(narr)] @@ -1655,7 +2690,18 @@ class FortranProgramWriter(object): class ReportWriter(object): + """Outputs a compilation analysis summary in JSON format. + + Creates `ir2.py` containing topological sort order and graph relationships + to verify pipeline execution properties. + """ + def write(self, ctx): + """Writes the IR details to `ir2.py`. + + Args: + ctx (CompilationContext): The compiled context. + """ import json dg = {k:list(v) for k,v in ctx.dependency.items()} @@ -1668,7 +2714,17 @@ class ReportWriter(object): class LatexWriter(object): + """Outputs the compiled average physical quantities LaTeX equations as a Python dictionary. + + Prints the mapped string representation of the dictionary directly to stdout. + """ + def write(self, ctx): + """Writes the LaTeX equation definitions dictionary to standard output. + + Args: + ctx (CompilationContext): The compiled context. + """ latex_lines = ["{"] for avg in ctx.averaged.values(): latex_lines.append(' "{}" : r"${}$",'.format(avg.name, avg.latex))