refactor: enforce SRP compliance (Phase 3)
This commit is contained in:
parent
eef9f59a56
commit
ad85581267
1 changed files with 346 additions and 340 deletions
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue