From ad8558126706623df139dcec36c7a1dafe24458a Mon Sep 17 00:00:00 2001 From: ignis Date: Thu, 4 Jun 2026 08:41:57 +0000 Subject: [PATCH] refactor: enforce SRP compliance (Phase 3) --- code/code_gen/post.py | 686 +++++++++++++++++++++--------------------- 1 file changed, 346 insertions(+), 340 deletions(-) diff --git a/code/code_gen/post.py b/code/code_gen/post.py index 341f94e..8a10022 100644 --- a/code/code_gen/post.py +++ b/code/code_gen/post.py @@ -96,6 +96,319 @@ 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: + REAL_ARRAY_LOOP = """ +! {{ comment }} +{% if decls_str -%} +block +{{ decls_str | indent(4, True) }} +{%- endif %} +do k = 1, nzp +do j = 1, nyp +do i = 1, nxp +{% if assigns_str -%} +{{ assigns_str | indent(4, True) }} + {{ array }}(i,j,k) = {{ rhs }} +{%- else -%} + {{ array }}(i,j,k) = {{ rhs }} +{%- endif %} +end do +end do +end do +{% if decls_str -%} +end block +{%- endif %} +""" + + FLUCTUATION_ARRAY_LOOP = """ +! {{ comment }} +do k = 1, nzp +do j = 1, nyp +do i = 1, nxp +{{ array }}(i,j,k) = {{ rhs }} +end do +end do +end do +""" + + AVG_ARRAY_SUM = """ +do k = 1, nzp +do j = 1, nyp +do i = 1, nxp +{{ name }}(i) = {{ name }}(i) + {{ arrname }} +end do +end do +end do +""" + + AVG_ARRAY_DIVIDE = """ +call MPI_ALLREDUCE(MPI_IN_PLACE, {{ name }}, nxp, MPI_REAL8, MPI_SUM, MPI_COMM_TASK, mpi_err) + +{{ name }} = {{ name }} {{ dWeight }} / denum +""" + + FMT_DECL_SUBARRAY = """ +! - file_handles and mpi_infos +integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_fh +integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_info +integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_filetype +""" + + FMT_INIT_SUBARRAY = """ +! init subarray datatype for {{ field_name }} +block + integer(4) :: sizes(3), subsizes(3), starts(3) + call MPI_INFO_CREATE({{ field_name }}_info, mpi_err) + call MPI_FILE_OPEN(MPI_COMM_TASK,'export-{{ field_name }}.dat',MPI_MODE_WRONLY+MPI_MODE_CREATE,{{ field_name }}_info,{{ field_name }}_fh,mpi_err) + sizes = (/ nxp, nyp, nzp /) + subsizes = (/ {{ len_xpts }}, {{ ye }} - {{ ys }} + 1, {{ ze }} - {{ zs }} + 1 /) + starts = (/ {{ xs }} - 1, {{ ys }} - 1, {{ zs }} - 1 /) + call MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, MPI_ORDER_FORTRAN, MPI_REAL8, {{ field_name }}_filetype, mpi_err) + call MPI_TYPE_COMMIT({{ field_name }}_filetype, mpi_err) +end block +""" + + FMT_FINAL_SUBARRAY = """ +! finalize +call MPI_FILE_CLOSE({{ field_name }}_fh, mpi_err) +call MPI_INFO_FREE({{ field_name }}_info, mpi_err) +call MPI_TYPE_FREE({{ field_name }}_filetype, mpi_err) +""" + + FMT_CALC_SUBARRAY = """ +! write to file via MPI Subarray +count = ({{ len_xpts }}) * ({{ ye }} - {{ ys }} + 1) * ({{ ze }} - {{ zs }} + 1) +offset = export_offset(fidx) * count * 8 +call MPI_FILE_WRITE_AT({{ field_name }}_fh, offset, {{ work_array }}, 1, {{ field_name }}_filetype, mpi_status, mpi_err) +""" + + FMT_DECL_LEGACY = """ +! - file_handles and mpi_infos +integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_fh +integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_info + +! - buffer +real(real64), allocatable, dimension(:,:,:) :: {{ field_name }}_export_array +integer, allocatable, dimension(:) :: {{ field_name }}_xpts +""" + + FMT_INIT_LEGACY = """ +! init +call MPI_INFO_CREATE({{ field_name }}_info, mpi_err) +call MPI_FILE_OPEN(MPI_COMM_TASK,'export-{{ field_name }}.dat',MPI_MODE_WRONLY+MPI_MODE_CREATE,{{ field_name }}_info,{{ field_name }}_fh,mpi_err) +allocate({{ field_name }}_export_array(1:{{ len_xpts }},{{ ys }}:{{ ye }},{{ zs }}:{{ ze }}), stat=ierr) +if (ierr /= 0) then + write(0,*) 'Error: allocation of {{ field_name }}_export_array failed on process', myid + call MPI_ABORT(MPI_COMM_TASK, 1, mpi_err) +end if +{{ field_name }}_export_array = 0. +allocate({{ field_name }}_xpts(1:{{ len_xpts }}), stat=ierr) +if (ierr /= 0) then + write(0,*) 'Error: allocation of {{ field_name }}_xpts failed on process', myid + call MPI_ABORT(MPI_COMM_TASK, 1, mpi_err) +end if +{{ xpts_init }} +""" + + FMT_FINAL_LEGACY = """ +! finalize +call MPI_FILE_CLOSE({{ field_name }}_fh, mpi_err) +call MPI_INFO_FREE({{ field_name }}_info, mpi_err) +deallocate({{ field_name }}_export_array) +deallocate({{ field_name }}_xpts) +""" + + FMT_CALC_LEGACY = """ +! copy to array for export +do k = {{ zs }}, {{ ze }} +do j = {{ ys }}, {{ ye }} +do i = 1, {{ len_xpts }} +{{ field_name }}_export_array(i,j,k) = {{ work_array }}({{ field_name }}_xpts(i),j,k) +end do +end do +end do + +! write to file +count = ({{ len_xpts }}) * ({{ ye }} - {{ ys }} + 1) * ({{ ze }} - {{ zs }} + 1) +offset = export_offset(fidx) * count * 8 +call MPI_FILE_WRITE_AT({{ field_name }}_fh, offset, {{ field_name }}_export_array, count, MPI_REAL8, mpi_status, mpi_err) +""" + + AVG_ARRAY_WRITE = """ +real(real64), dimension(nxp) :: xbuffer +integer :: i + +open (200, file="qEdge_X.dat") +write (200,*) output_header +do i=1,nxp + write (200,'({{ num_args }}e20.10)') real(i)*hxp, {{ formatted_avglist }} +end do +close (200) + +open (200, file="d1.dat") +{{ deriv1_lines }} +close (200) + +open (200, file="d2.dat") +{{ deriv2_lines }} +close (200) +""" + + +class FortranCodeGenerator(object): + def __init__(self, fdict): + self.fdict = fdict + + def generate_code(self, field, alloc=None): + method_name = 'visit_' + field.__class__.__name__ + '_code' + visitor = getattr(self, method_name, self.generic_code) + return visitor(field, alloc) + + def generate_decl(self, field): + method_name = 'visit_' + field.__class__.__name__ + '_decl' + visitor = getattr(self, method_name, self.generic_decl) + return visitor(field) + + def generate_alloc(self, field): + method_name = 'visit_' + field.__class__.__name__ + '_alloc' + visitor = getattr(self, method_name, self.generic_alloc) + return visitor(field) + + def generate_free(self, field): + method_name = 'visit_' + field.__class__.__name__ + '_free' + visitor = getattr(self, method_name, self.generic_free) + return visitor(field) + + def generate_avg(self, field): + 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): + return "" + + def generic_decl(self, field): + real_array_decl = "real(real64), allocatable, dimension({1}) :: {0}" + return real_array_decl.format(field.name, field.dim) + + def generic_alloc(self, field): + return make_allocate(field.name, field.shape) + + def generic_free(self, field): + real_array_free = "deallocate({})" + return real_array_free.format(field.name) + + def generic_avg(self, field): + return "" + + # --- Visit Methods --- + + def visit_FieldExporter_code(self, exporter, alloc=None): + exporter.params["work_array"] = exporter.parent.array + if exporter.use_subarray: + return Template(FortranTemplateStore.FMT_CALC_SUBARRAY).render(**exporter.params) + else: + return Template(FortranTemplateStore.FMT_CALC_LEGACY).render(**exporter.params) + + def visit_FieldExporter_decl(self, exporter): + 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): + 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): + 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): + field.array = alloc[field.name] if alloc else field.name + + opt = SympyOptimizer.get_instance(self.fdict) + rhs, cse_decls, cse_assigns = opt.optimize_field(field.name, alloc) + + decls_str = "\n".join(cse_decls) if cse_decls else "" + assigns_str = "\n".join(cse_assigns) if cse_assigns else "" + + calculation_code = Template(FortranTemplateStore.REAL_ARRAY_LOOP).render( + comment=field.comment, + decls_str=decls_str, + assigns_str=assigns_str, + array=field.array, + rhs=rhs + ) + + export_code = self.generate_code(field.exporter) if field.export_on() else "" + return calculation_code + export_code + + def visit_FluctuationField_code(self, field, alloc=None): + field.array = alloc[field.name] if alloc else field.name + rhs = ExpToCode(self.fdict).transform(field.field.exp) + + if field.field.is_fluctuation(): + rhs = rhs.format(field.w) + + return Template(FortranTemplateStore.FLUCTUATION_ARRAY_LOOP).render( + comment=field.comment, + array=field.array, + rhs=rhs + ) + + def visit_PrimaryField_code(self, field, alloc=None): + return "! {} is read from file".format(field.name) + + def visit_PrimaryField_decl(self, field): + return "! {} is read from file".format(field.name) + + def visit_PrimaryField_alloc(self, field): + return "! {} is read from file".format(field.name) + + def visit_PrimaryField_free(self, field): + return "! {} is read from file".format(field.name) + + def visit_DerivedField_code(self, field, alloc=None): + 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): + arrname = self.fdict[field.tgt].array + "(i,j,k)" + if field.weighted is not None: + arrname = arrname + " * " + field.w.array + "(i,j,k)" + + return Template(FortranTemplateStore.AVG_ARRAY_SUM).render(name=field.name, arrname=arrname) + + def visit_AveragedField_avg(self, field): + 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): + avgarr = "{}(i)" + deriv1_avgarr = """call ddx1d ( xbuffer, {} ) ; write (200,*) xbuffer""" + deriv2_avgarr = """call d2dx1d ( xbuffer, {} ) ; write (200,*) xbuffer""" + + num_args = len(avglist) + 1 + formatted_avglist = ", ".join(map(avgarr.format, avglist)) + deriv1_lines = "\n".join(map(deriv1_avgarr.format, avglist)) + deriv2_lines = "\n".join(map(deriv2_avgarr.format, avglist)) + + write_avg = Template(FortranTemplateStore.AVG_ARRAY_WRITE).render( + num_args=num_args, + formatted_avglist=formatted_avglist, + deriv1_lines=deriv1_lines, + deriv2_lines=deriv2_lines + ) + return write_avg + + @v_args(inline=True) class LarkToSympy(Transformer): """Lark AST의 수학적 노드들을 SymPy 기호 수식 객체로 변환하는 Transformer 클래스입니다. @@ -800,23 +1113,6 @@ class DependencyNode(object): return fset -class Generatable(object): - """Fortran 소스코드 생성이 가능한 필드에 대한 인터페이스입니다.""" - def code(self, alloc=None): - raise NotImplementedError - - def code_decl(self): - real_array_decl = "real(real64), allocatable, dimension({1}) :: {0}" - return real_array_decl.format(self.name, self.dim) - - def code_alloc(self): - return make_allocate(self.name, self.shape) - - def code_free(self): - real_array_free = "deallocate({})" - return real_array_free.format(self.name) - - class FieldBase (DependencyNode): """모든 물리 필드 객체의 최상위 기본 클래스로서 공통 데이터 구조를 정의합니다.""" def __init__ (self, name, fdict): @@ -833,109 +1129,14 @@ class FieldBase (DependencyNode): return self.name -class FieldExporter (Generatable): +class FieldExporter (object): """물리 필드 데이터를 병렬 분산 디스크 시스템으로 직접 추출(Export)하는 고성능 MPI-IO 서브루틴 블록을 - 생성하는 템플릿 처리 클래스입니다. - - 1. Subarray 방식 (기본값): MPI_TYPE_CREATE_SUBARRAY를 사용하여 3차원 격자의 전체 물리 도메인에서 - 현재 MPI 랭크가 맡고 있는 일부분의 3D 블록을 통째로 오프셋과 크기를 지정해 병렬 쓰기함으로써 병목을 최소화합니다. - 2. Legacy Copy 방식 (특정 X 좌표들만 샘플링하여 쓸 때): 전체 배열 중 특정 좌표들만 추출하여 - 임시 전송 배열로 카피한 뒤 단일 스트림 형태로 쓰기를 수행합니다. + 정의하는 도메인 데이터 클래스입니다. """ - - mpi_io_decl=''' + mpi_io_decl = """ ! field exporter common integer(kind=MPI_OFFSET_KIND) :: offset -''' - - # Subarray 버전 템플릿 - fmt_decl_subarray=''' -! - file_handles and mpi_infos -integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_fh -integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_info -integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_filetype -''' - - fmt_init_subarray=''' -! init subarray datatype for {{ field_name }} -block - integer(4) :: sizes(3), subsizes(3), starts(3) - call MPI_INFO_CREATE({{ field_name }}_info, mpi_err) - call MPI_FILE_OPEN(MPI_COMM_TASK,'export-{{ field_name }}.dat',MPI_MODE_WRONLY+MPI_MODE_CREATE,{{ field_name }}_info,{{ field_name }}_fh,mpi_err) - sizes = (/ nxp, nyp, nzp /) - subsizes = (/ {{ len_xpts }}, {{ ye }} - {{ ys }} + 1, {{ ze }} - {{ zs }} + 1 /) - starts = (/ {{ xs }} - 1, {{ ys }} - 1, {{ zs }} - 1 /) - call MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, MPI_ORDER_FORTRAN, MPI_REAL8, {{ field_name }}_filetype, mpi_err) - call MPI_TYPE_COMMIT({{ field_name }}_filetype, mpi_err) -end block -''' - - fmt_final_subarray=''' -! finalize -call MPI_FILE_CLOSE({{ field_name }}_fh, mpi_err) -call MPI_INFO_FREE({{ field_name }}_info, mpi_err) -call MPI_TYPE_FREE({{ field_name }}_filetype, mpi_err) -''' - - fmt_calc_subarray=''' -! write to file via MPI Subarray -count = ({{ len_xpts }}) * ({{ ye }} - {{ ys }} + 1) * ({{ ze }} - {{ zs }} + 1) -offset = export_offset(fidx) * count * 8 -call MPI_FILE_WRITE_AT({{ field_name }}_fh, offset, {{ work_array }}, 1, {{ field_name }}_filetype, mpi_status, mpi_err) -''' - - # Legacy copy 버전 (fallback) - fmt_decl_legacy=''' -! - file_handles and mpi_infos -integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_fh -integer(kind=MPI_INTEGER_KIND) :: {{ field_name }}_info - -! - buffer -real(real64), allocatable, dimension(:,:,:) :: {{ field_name }}_export_array -integer, allocatable, dimension(:) :: {{ field_name }}_xpts -''' - - fmt_init_legacy=''' -! init -call MPI_INFO_CREATE({{ field_name }}_info, mpi_err) -call MPI_FILE_OPEN(MPI_COMM_TASK,'export-{{ field_name }}.dat',MPI_MODE_WRONLY+MPI_MODE_CREATE,{{ field_name }}_info,{{ field_name }}_fh,mpi_err) -allocate({{ field_name }}_export_array(1:{{ len_xpts }},{{ ys }}:{{ ye }},{{ zs }}:{{ ze }}), stat=ierr) -if (ierr /= 0) then - write(0,*) 'Error: allocation of {{ field_name }}_export_array failed on process', myid - call MPI_ABORT(MPI_COMM_TASK, 1, mpi_err) -end if -{{ field_name }}_export_array = 0. -allocate({{ field_name }}_xpts(1:{{ len_xpts }}), stat=ierr) -if (ierr /= 0) then - write(0,*) 'Error: allocation of {{ field_name }}_xpts failed on process', myid - call MPI_ABORT(MPI_COMM_TASK, 1, mpi_err) -end if -{{ xpts_init }} -''' - - fmt_final_legacy=''' -! finalize -call MPI_FILE_CLOSE({{ field_name }}_fh, mpi_err) -call MPI_INFO_FREE({{ field_name }}_info, mpi_err) -deallocate({{ field_name }}_export_array) -deallocate({{ field_name }}_xpts) -''' - - fmt_calc_legacy=''' -! copy to array for export -do k = {{ zs }}, {{ ze }} -do j = {{ ys }}, {{ ye }} -do i = 1, {{ len_xpts }} -{{ field_name }}_export_array(i,j,k) = {{ work_array }}({{ field_name }}_xpts(i),j,k) -end do -end do -end do - -! write to file -count = ({{ len_xpts }}) * ({{ ye }} - {{ ys }} + 1) * ({{ ze }} - {{ zs }} + 1) -offset = export_offset(fidx) * count * 8 -call MPI_FILE_WRITE_AT({{ field_name }}_fh, offset, {{ field_name }}_export_array, count, MPI_REAL8, mpi_status, mpi_err) -''' +""" def __init__ (self, name, attr, parent): self.name = name @@ -963,10 +1164,9 @@ end do self.params.setdefault("xpts_init", Template(fmt_xpts_init).render(**self.params)) try: - # Sampling at listed x coordinates + # Sampling at listed x coordinates fmt_xpts_init_list = "{{ field_name }}_xpts = (/ {{ list_xpts }} /)" - import sys raw_xpts = self.params["xpts"] int_xpts = list(map(int, raw_xpts.split())) len_xpts = len(int_xpts) @@ -979,35 +1179,7 @@ end do self.use_subarray = ("xpts" not in self.params) - def code (self): - self.params["work_array"] = self.parent.array - if self.use_subarray: - return Template(FieldExporter.fmt_calc_subarray).render(**self.params) - else: - return Template(FieldExporter.fmt_calc_legacy).render(**self.params) - - def code_decl (self): - if self.use_subarray: - return Template(FieldExporter.fmt_decl_subarray).render(**self.params) - else: - return Template(FieldExporter.fmt_decl_legacy).render(**self.params) - - def code_alloc (self): - if self.use_subarray: - return Template(FieldExporter.fmt_init_subarray).render(**self.params) - else: - return Template(FieldExporter.fmt_init_legacy).render(**self.params) - - def code_free (self): - if self.use_subarray: - return Template(FieldExporter.fmt_final_subarray).render(**self.params) - else: - return Template(FieldExporter.fmt_final_legacy).render(**self.params) - - - - -class Field (FieldBase, Generatable): +class Field (FieldBase): """일반 대입 계산 변수를 관리하며, 3차원 격자점 루프 코드 생성을 담당하는 핵심 클래스입니다. SymPy 최적화 및 CSE 적용 코드를 루프 본문에 결합합니다. """ @@ -1035,55 +1207,8 @@ class Field (FieldBase, Generatable): def export_on (self): return self.exporter is not None - def code (self, alloc=None): - self.array = alloc[self.name] if alloc else self.name - # SymPy 기호 수식 최적화 및 루프 내 CSE 적용 스칼라 추출 - opt = SympyOptimizer.get_instance(self.fdict) - rhs, cse_decls, cse_assigns = opt.optimize_field(self.name, alloc) - - decls_str = "\n".join(cse_decls) if cse_decls else "" - assigns_str = "\n".join(cse_assigns) if cse_assigns else "" - - # 생성될 Fortran 3차원 루프 템플릿 - # CSE 최적화에 의해 추출된 로컬 임시 스칼라 할당문(assigns_str)을 3중 루프 i, j, k 본문 내부에서 먼저 연산하도록 삽입하고, - # 이 변수들을 사용하여 우변(rhs)을 연산하고 최종 배열(array(i,j,k))에 저장합니다. - real_array_loop = """ -! {{ comment }} -{% if decls_str -%} -block -{{ decls_str | indent(4, True) }} -{%- endif %} -do k = 1, nzp -do j = 1, nyp -do i = 1, nxp -{% if assigns_str -%} -{{ assigns_str | indent(4, True) }} - {{ array }}(i,j,k) = {{ rhs }} -{%- else -%} - {{ array }}(i,j,k) = {{ rhs }} -{%- endif %} -end do -end do -end do -{% if decls_str -%} -end block -{%- endif %} -""" - calculation_code = Template(real_array_loop).render( - comment=self.comment, - decls_str=decls_str, - assigns_str=assigns_str, - array=self.array, - rhs=rhs - ) - - export_code = ( self.exporter.code() if self.export_on() else "") - - return calculation_code + export_code - - -class FluctuationField (FieldBase, Generatable): +class FluctuationField (FieldBase): """물리 필드의 난류 변동 성분(Fluctuation, u' = u - )을 계산하기 위한 변수 클래스입니다. 수식 내의 u' 기호를 평균량과의 차이 수식으로 팽창하여 할당합니다. """ @@ -1109,30 +1234,6 @@ class FluctuationField (FieldBase, Generatable): self.comment = self.name + " = " + self.comment - def code (self, alloc=None): - self.array = alloc[self.name] if alloc else self.name - - rhs = ExpToCode(self.fdict).transform(self.field.exp) - - if self.field.is_fluctuation(): - rhs = rhs.format(self.w) - - real_array_loop = """ -! {{ comment }} -do k = 1, nzp -do j = 1, nyp -do i = 1, nxp -{{ array }}(i,j,k) = {{ rhs }} -end do -end do -end do -""" - return Template(real_array_loop).render( - comment=self.comment, - array=self.array, - rhs=rhs - ) - @classmethod def id (cls, w, field): if w: @@ -1156,7 +1257,7 @@ class PrimaryField (FieldBase): self.latex_given = None -class DerivedField (FieldBase, Generatable): +class DerivedField (FieldBase): """수치 공간 미분(ddx, d2dy 등)을 수행하여 계산되는 유도 필드 클래스입니다. Fortran 수치 차분 패키지 서브루틴(Compact.f90 에 구현된 dfnonp, dfp 등)의 동적 호출 코드를 출력합니다. @@ -1172,14 +1273,8 @@ class DerivedField (FieldBase, Generatable): partial = differential_operator_registry.get_latex_symbol(op) self.latex = partial + "(" + fdict[v].latex + ")" - def code (self, alloc=None): - self.array = alloc[self.name] if alloc else self.name - varray = alloc[self.v] if alloc else self.v - # 예: call ddx ( xyzbuffer0, u ) 형태의 서브루틴 호출 코드를 작성 - return "call {0} ( {2}, {1} )".format(self.op, varray, self.array) - -class AveragedField (FieldBase, Generatable): +class AveragedField (FieldBase): """특정 물리 필드를 격자의 동질 차원(예: X 방향 1D선상 평균)에 대해 공간 통계 평균(Average) 연산을 수행하는 1차원 필드 클래스입니다. """ @@ -1219,37 +1314,6 @@ class AveragedField (FieldBase, Generatable): self.dep.add(w) self.latex += ("_{{{}}}".format(w)) - - def code (self, alloc=None): - # 3차원 루프를 돌며 Y, Z 축에 대해 값을 누적합 연산하는 구문 - avg_array_sum = """ -do k = 1, nzp -do j = 1, nyp -do i = 1, nxp -{{ name }}(i) = {{ name }}(i) + {{ arrname }} -end do -end do -end do -""" - arrname = self.fdict[self.tgt].array + "(i,j,k)" - if self.weighted is not None: - arrname = arrname + " * " + self.w.array + "(i,j,k)" - - return Template(avg_array_sum).render(name=self.name, arrname=arrname) - - - def code_avg (self): - # 병렬 랭크 간 MPI_ALLREDUCE를 통해 X방향 라인별 총합을 싱크한 뒤, - # 전체 그리드 면적(denum = nyp * nzp) 및 가중 평균 값으로 나누어 실제 수학적 평균값 산출 - avg_array_divide = """ -call MPI_ALLREDUCE(MPI_IN_PLACE, {{ name }}, nxp, MPI_REAL8, MPI_SUM, MPI_COMM_TASK, mpi_err) - -{{ name }} = {{ name }} {{ dWeight }} / denum -""" - dWeight = (f"/ avg_{self.weighted}" if self.weighted else "") - - return Template(avg_array_divide).render(name=self.name, dWeight=dWeight) - def isWeighted (self): return self.weighted is not None @@ -1466,44 +1530,25 @@ class Stage3(): allvar = dict(self.derived) allvar.update(self.averaged) - decl = "\n".join(allvar[v].code_decl() for v in set(self.pass1+self.pass2)) - alloc = "\n".join(allvar[v].code_alloc() for v in set(self.pass1+self.pass2)) - free = "\n".join(allvar[v].code_free() for v in set(self.pass1+self.pass2)) + generator = FortranCodeGenerator(allvar) - calc1 = "\n".join(allvar[v].code() for v in self.pass1) - calc2 = "\n".join(allvar[v].code() for v in self.pass2) + decl = "\n".join(generator.generate_decl(allvar[v]) for v in set(self.pass1+self.pass2)) + alloc = "\n".join(generator.generate_alloc(allvar[v]) for v in set(self.pass1+self.pass2)) + free = "\n".join(generator.generate_free(allvar[v]) for v in set(self.pass1+self.pass2)) - set1 = [a.name for a in filter(AveragedField.pass1, self.averaged.values())] - set2 = [a.name for a in filter(AveragedField.pass2, self.averaged.values())] + calc1 = "\n".join(generator.generate_code(allvar[v]) for v in self.pass1) + calc2 = "\n".join(generator.generate_code(allvar[v]) for v in self.pass2) - set1.sort() - set2.sort() + set1 = sorted([a.name for a in filter(AveragedField.pass1, self.averaged.values())]) + set2 = sorted([a.name for a in filter(AveragedField.pass2, self.averaged.values())]) - avg1 = "\n".join(allvar[v].code_avg() for v in set1) - avg2 = "\n".join(allvar[v].code_avg() for v in set2) + avg1 = "\n".join(generator.generate_avg(allvar[v]) for v in set1) + avg2 = "\n".join(generator.generate_avg(allvar[v]) for v in set2) hfmt = 'character (len = *), parameter :: output_header="{}"' declh = hfmt.format(" ".join(["x"] + set1 + set2)) - avg_array_write = ''' -integer :: i - -open (200, file="qEdge_X.dat") - -write (200,*) output_header - -do i=1,nxp - write (200,'({0}e20.10)') real(i)*hxp, {1} -end do - -close (200) -''' - - avgarr = "{}(i)" - write_avg = avg_array_write.format( - len(self.averaged)+1, - ", ".join(map(avgarr.format, set1+set2)) - ) + write_avg = generator.generate_write_avg(set1+set2) md = {} md["module_name"] = "terms" @@ -1651,47 +1696,6 @@ class Stage4(): return decl, alloc, free - def write_avg_codes (self, avglist): - """연산 최종 완료 후 평균화가 끝난 1D 결과 데이터셋 및 미분량을 - 텍스트 파일(qEdge_X.dat, d1.dat, d2.dat)에 형식에 맞추어 라이팅하는 Fortran 서브루틴 블록을 생성합니다. - """ - avg_array_write = ''' -real(real64), dimension(nxp) :: xbuffer -integer :: i - -open (200, file="qEdge_X.dat") -write (200,*) output_header -do i=1,nxp - write (200,'({{ num_args }}e20.10)') real(i)*hxp, {{ formatted_avglist }} -end do -close (200) - -open (200, file="d1.dat") -{{ deriv1_lines }} -close (200) - -open (200, file="d2.dat") -{{ deriv2_lines }} -close (200) -''' - avgarr = "{}(i)" - deriv1_avgarr = """call ddx1d ( xbuffer, {} ) ; write (200,*) xbuffer""" - deriv2_avgarr = """call d2dx1d ( xbuffer, {} ) ; write (200,*) xbuffer""" - - num_args = len(self.averaged) + 1 - formatted_avglist = ", ".join(map(avgarr.format, avglist)) - deriv1_lines = "\n".join(map(deriv1_avgarr.format, avglist)) - deriv2_lines = "\n".join(map(deriv2_avgarr.format, avglist)) - - write_avg = Template(avg_array_write).render( - num_args=num_args, - formatted_avglist=formatted_avglist, - deriv1_lines=deriv1_lines, - deriv2_lines=deriv2_lines - ) - return write_avg - - def print_program (self): """4단계 최적화(공유 Pooling 버퍼 매핑 및 SymPy CSE 치환)가 완료된 가장 고성능의 최종 Fortran 모듈 코드를 생성하기 위해 Jinja2용 딕셔너리를 빌드합니다. @@ -1702,6 +1706,8 @@ close (200) allvar = dict(self.derived) allvar.update(self.averaged) + generator = FortranCodeGenerator(allvar) + # 평균 변수들을 순서대로 분배 set1 = sorted([a.name for a in filter(AveragedField.pass1, self.averaged.values())]) set2 = sorted([a.name for a in filter(AveragedField.pass2, self.averaged.values())]) @@ -1719,25 +1725,25 @@ close (200) declarr, allocarr, freearr = self.work_array_codes() # 1차원 평균 물리량 배열들의 선언/할당 코드 생성 - declavg = "\n".join(self.averaged[v].code_decl() for v in sorted(self.averaged)) - allocavg = "\n".join(self.averaged[v].code_alloc() for v in sorted(self.averaged)) - freeavg = "\n".join(self.averaged[v].code_free() for v in sorted(self.averaged)) + declavg = "\n".join(generator.generate_decl(self.averaged[v]) for v in sorted(self.averaged)) + allocavg = "\n".join(generator.generate_alloc(self.averaged[v]) for v in sorted(self.averaged)) + freeavg = "\n".join(generator.generate_free(self.averaged[v]) for v in sorted(self.averaged)) # 병렬 파일 쓰기(MPI Subarray)를 위한 MPI 리소스 선언/할당 코드 생성 - decl_export = "\n".join(v.exporter.code_decl() for v in set_export_on) - alloc_export = "\n".join(v.exporter.code_alloc() for v in set_export_on) - free_export = "\n".join(v.exporter.code_free() for v in set_export_on) + decl_export = "\n".join(generator.generate_decl(v.exporter) for v in set_export_on) + alloc_export = "\n".join(generator.generate_alloc(v.exporter) for v in set_export_on) + free_export = "\n".join(generator.generate_free(v.exporter) for v in set_export_on) # Pass 1과 Pass 2 루프 내부 본문 연산 코드들을 생성 (각자 버퍼 맵 alloc1, alloc2 적용) - sub_calc1 = "\n".join(allvar[v].code(self.alloc1) for v in self.pass1 if v in self.averaged or v in self.alloc1) - sub_calc2 = "\n".join(allvar[v].code(self.alloc2) for v in self.pass2 if v in self.averaged or v in self.alloc2) + sub_calc1 = "\n".join(generator.generate_code(allvar[v], self.alloc1) for v in self.pass1 if v in self.averaged or v in self.alloc1) + sub_calc2 = "\n".join(generator.generate_code(allvar[v], self.alloc2) for v in self.pass2 if v in self.averaged or v in self.alloc2) # 평균 누적 연산 코드 생성 - sub_avg1 = "\n".join(allvar[v].code_avg() for v in set1) - sub_avg2 = "\n".join(allvar[v].code_avg() for v in set2) + sub_avg1 = "\n".join(generator.generate_avg(allvar[v]) for v in set1) + sub_avg2 = "\n".join(generator.generate_avg(allvar[v]) for v in set2) # 파일 쓰기 루틴 코드 생성 - sub_write_avg = self.write_avg_codes(set1+set2) + sub_write_avg = generator.generate_write_avg(set1+set2) md = {} md["module_name"] = "terms"