refactor: enforce SRP compliance (Phase 3)
All checks were successful
CI Test Suite / run-tests (push) Successful in 1m18s
Deploy Documentation / build-and-deploy (push) Successful in 54s

This commit is contained in:
ignis 2026-06-04 08:41:57 +00:00
parent eef9f59a56
commit ad85581267

View file

@ -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
@ -966,7 +1167,6 @@ end do
# 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_w>)을 계산하기 위한 변수 클래스입니다.
수식 내의 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"