Devin.KR

완성 프로젝트 - 막대 열 전달 시뮬레이션

개발자KR 조회 0

이 장에서 배우는 것

이 장에서는 앞 장들에서 익힌 문법과 계산 기법을 한 프로그램으로 묶는다. 길이 1 m인 금속 막대의 한쪽 끝을 뜨겁게 달구었을 때 열이 막대를 따라 퍼지는 모습을 시간 순서대로 계산한다. 이어서 관측소의 시간별 기온·강수 자료를 요약하는 보고서를 같은 프로그램에 붙인다. 코드는 설정, 계산, 출력을 맡는 세 모듈과 이들을 부르는 주 프로그램으로 나눈다.

  • 1차원 열 방정식을 명시적 차분으로 풀어 한 시간 단계의 갱신식을 만든다.
  • 안정 조건 r ≤ 0.5의 뜻을 이해하고, 시뮬레이션 시작 전에 검사하는 코드를 쓴다.
  • 설정·계산·출력을 모듈 세 개로 나누고 컴파일 순서를 맞춘다.
  • 서식 지정자로 자릿수를 고정한 결과 표와 관측 자료 요약 보고서를 출력한다.

문제 상황

기상 관측소에는 기온과 강수량 같은 자료가 시간별로 쌓인다. 관측소 장비 중에는 온도 센서를 금속 막대에 붙여 바깥 공기와 열을 주고받게 하는 것이 있다. 센서 설계자는 막대 한쪽 끝의 온도가 바뀌면 반대쪽 끝까지 얼마나 걸려 전해지는지 알고 싶다. 실제 막대를 만들어 재기 전에 계산으로 먼저 예측해 보려는 것이다.

이런 계산 프로그램은 처음에는 파일 하나에 쓰기 시작해도 금세 길어진다. 격자 크기를 바꾸고, 시간 간격을 조절하고, 출력 형식을 고치는 일이 한 파일 안에서 뒤엉킨다. 실무에서는 "무엇을 계산할지(설정)", "어떻게 계산할지(계산)", "어떻게 보여줄지(출력)"를 나누어 두는 편이 수정하기 쉽다. 시간 간격을 너무 크게 잡아 계산이 엉뚱한 값으로 폭주하는 사고도 흔하므로, 계산을 시작하기 전에 조건을 검사하는 장치도 필요하다.

열 방정식과 명시적 차분

막대를 칸으로 나눈다

막대를 같은 간격 Δx(델타 x)로 나눈 점 n개로 생각한다. 이 점들을 격자(grid)라 하고, 왼쪽부터 번호 1, 2, …, n을 붙인다. 각 점의 온도를 u(i)라 부른다. 시간도 같은 방식으로 Δt씩 끊어서, 0번째 단계, 1번째 단계처럼 센다.

막대 속 열의 이동은 열 방정식으로 쓴다. 한 점의 온도가 변하는 빠르기는 그 점의 온도가 이웃 평균보다 얼마나 높거나 낮은지에 비례한다는 내용이다. 비례 상수 α(알파)는 열확산계수로, 재질마다 정해진 값이다. 이 장에서는 α = 1.0e-4 (m²/s)를 쓴다.

이웃 평균과의 차이를 격자 위에서 쓰면 u(i-1) − 2·u(i) + u(i+1)이다. 이 값이 양수이면 이웃이 더 따뜻하므로 그 점의 온도는 오른다. 이 양에 r을 곱해 현재 값에 더하는 것이 명시적 차분(explicit finite difference)의 한 단계다.

u(i)의 새 값 = u(i) + r * ( u(i-1) - 2*u(i) + u(i+1) )

r = α * Δt / Δx²

"명시적"이라는 말은 새 값이 옛 값만으로 곧바로 계산된다는 뜻이다. 연립방정식을 풀 필요가 없어 코드가 짧다. 아래 그림은 한 점의 새 값이 이웃 세 점의 옛 값으로 만들어지는 모습이다.

한 칸의 새 값은 자신과 좌우 이웃, 세 칸의 옛 값만으로 계산되고 양 끝 칸은 값이 고정된다.

양 끝 점은 이 식으로 갱신하지 않는다. 왼쪽 끝은 100도로 가열하고 오른쪽 끝은 20도로 유지한다고 정해 두기 때문이다. 이렇게 끝 값을 정해 두는 조건을 경계 조건(boundary condition)이라 한다. 배열로는 u(2:n-1)만 갱신하면 된다.

Fortran 배열 대입은 오른쪽 변을 모두 계산한 뒤에 왼쪽에 넣는다. 그래서 u(2:n-1) = u(2:n-1) + r*(u(1:n-2) - 2*u(2:n-1) + u(3:n))이라고 쓰면 모든 점이 같은 시각의 옛 값을 보고 갱신된다. 반복문으로 한 점씩 고쳐 쓸 때와는 다른 점이며, 뒤의 "자주 틀리는 것"에서 다시 다룬다.

안정 조건

명시적 차분에는 함정이 있다. Δt를 너무 크게 잡으면 결과가 매 단계 부호를 바꾸며 커져서 곧 무의미한 숫자가 된다. 갱신식을 u(i), u(i-1), u(i+1)의 계수로 다시 묶으면 이유가 보인다.

새 u(i) = r*u(i-1) + (1 - 2r)*u(i) + r*u(i+1)

r이 0.5 이하이면 세 계수가 모두 0 이상이고 합은 1이다. 새 값은 이웃 세 값의 가중평균이므로 옛 값의 최댓값을 넘지 못한다. r이 0.5를 넘으면 가운데 계수 1 − 2r이 음수가 되어, 한 점이 오히려 옛 값의 반대 방향으로 튀어 나간다. 이것이 안정 조건 r ≤ 0.5의 뜻이다.

안정수 r에 따라 한 단계 갱신의 성질이 달라진다
r가운데 계수 1 − 2r결과
0.250.5안정. 이웃의 가중평균이다
0.50안정의 한계. 이웃 둘의 평균이다
0.6-0.2불안정. 부호가 번갈아 바뀌며 커진다

r = α·Δt/Δx²이므로 격자를 두 배 촘촘하게 하면(Δx를 절반으로) Δt는 4분의 1로 줄여야 한다. 격자를 촘촘히 할수록 시간 단계 수가 급격히 늘어나는 이유다. 이 장의 설정에서는 Δx = 0.2 m, α = 1.0e-4, Δt = 100 s이므로 r = 1.0e-4 × 100 / 0.04 = 0.25가 되어 한계의 절반이다.

검사는 시뮬레이션을 시작하기 전에 한 번만 하면 된다. r은 설정값에서 정해지고 계산 도중에는 변하지 않기 때문이다. 한계를 넘으면 error stop으로 프로그램을 즉시 끝낸다.

모듈로 나눈 설계

세 모듈과 주 프로그램

코드를 "설정, 계산, 출력"으로 나누면 각 파일이 한 가지 이유로만 바뀐다. 격자를 바꾸고 싶으면 설정만, 갱신식을 바꾸고 싶으면 계산만, 표의 모양을 바꾸고 싶으면 출력만 고친다.

파일마다 하나의 책임을 맡는다
파일책임공개하는 이름
config_mod.f90실수 종류와 설정값 묶음dp, sim_config, default_config
heat_mod.f90안정수 계산, 초기화, 한 단계 갱신stability_number, init_rod, step_explicit
report_mod.f90표와 관측 자료 요약 출력print_header, print_row, print_obs_summary
main.f90검사하고, 반복하고, 출력을 부른다(주 프로그램)

설정값 여덟 개는 따로따로 넘기지 않고 파생 타입 sim_config에 묶었다. 계산 모듈과 출력 모듈은 모두 이 타입을 받으므로, 설정 항목이 늘어도 서브루틴의 인수 목록은 그대로다. 각 모듈은 private을 기본으로 두고 필요한 이름만 public으로 공개한다. 바깥에서 건드릴 필요가 없는 내부 이름이 섞이지 않게 하려는 것이다.

설정 모듈을 계산과 출력 모듈이 쓰고, 주 프로그램이 세 모듈을 모두 쓴다.

컴파일 순서

모듈을 쓰는 파일은 그 모듈이 먼저 컴파일되어 있어야 한다. 컴파일러는 모듈을 컴파일할 때 .mod 파일을 만들고, use를 만나면 그 파일을 읽는다. 따라서 명령줄에는 쓰이는 쪽을 먼저, 쓰는 쪽을 나중에 적는다. 그림의 화살표를 거슬러 가면 config_mod, heat_mod, report_mod, main 순서가 된다. 공식 문서의 모듈 설명은 GNU Fortran 문서에서 확인할 수 있다.

관측 자료 요약 보고서

보고서 부분은 앞 장들에서 다룬 배열 함수만으로 만든다. 평균은 sum을 원소 수로 나눈 값, 최고·최저 시각은 maxloc·minloc이 돌려주는 위치로 구한다. 위치는 배열 안의 번호이므로 첫 관측 시각을 더해 시각으로 바꾼다. 비가 온 시간은 count(rain > 0.0_dp)로 센다. 이 장에서는 파일 입력을 다시 쓰지 않고, 6시부터 17시까지 12시간의 값을 프로그램 안에 상수 배열로 둔다. 시뮬레이션과 보고서는 서로 값을 주고받지 않으며, 같은 프로그램이 차례로 출력할 뿐이다.

완성 코드

네 파일을 같은 디렉터리에 만든다. 컴파일 순서대로 모듈 세 개를 먼저, main.f90을 마지막에 둔다.

config_mod.f90

module config_mod
  implicit none
  private
  public :: dp, sim_config, default_config

  integer, parameter :: dp = selected_real_kind(15, 307)

  type :: sim_config
    integer  :: nx      ! 격자점 수
    integer  :: nsteps  ! 시간 단계 수
    integer  :: every   ! 몇 단계마다 표에 한 줄 쓸지
    real(dp) :: length  ! 막대 길이 (m)
    real(dp) :: alpha   ! 열확산계수 (m^2/s)
    real(dp) :: dt      ! 시간 간격 (s)
    real(dp) :: t_init  ! 처음 온도
    real(dp) :: t_left  ! 왼쪽 끝 고정 온도
    real(dp) :: t_right ! 오른쪽 끝 고정 온도
  end type sim_config

contains

  function default_config() result(c)
    type(sim_config) :: c
    c%nx = 6
    c%nsteps = 9
    c%every = 3
    c%length = 1.0_dp
    c%alpha = 1.0e-4_dp
    c%dt = 100.0_dp
    c%t_init = 20.0_dp
    c%t_left = 100.0_dp
    c%t_right = 20.0_dp
  end function default_config

end module config_mod

heat_mod.f90

module heat_mod
  use config_mod, only: dp, sim_config
  implicit none
  private
  public :: stability_number, init_rod, step_explicit

contains

  pure function stability_number(c) result(r)
    type(sim_config), intent(in) :: c
    real(dp) :: r, dx
    dx = c%length / real(c%nx - 1, dp)
    r = c%alpha * c%dt / dx**2
  end function stability_number

  subroutine init_rod(c, u)
    type(sim_config), intent(in) :: c
    real(dp), intent(out) :: u(:)
    u = c%t_init
    u(1) = c%t_left
    u(size(u)) = c%t_right
  end subroutine init_rod

  subroutine step_explicit(r, u)
    real(dp), intent(in) :: r
    real(dp), intent(inout) :: u(:)
    integer :: n
    n = size(u)
    ! 오른쪽 변을 모두 계산한 뒤 대입하므로 모든 점이 옛 값을 본다
    u(2:n-1) = u(2:n-1) + r * (u(1:n-2) - 2.0_dp * u(2:n-1) + u(3:n))
  end subroutine step_explicit

end module heat_mod

report_mod.f90

module report_mod
  use config_mod, only: dp, sim_config
  implicit none
  private
  public :: print_header, print_row, print_obs_summary

contains

  subroutine print_header(c, r)
    type(sim_config), intent(in) :: c
    real(dp), intent(in) :: r
    real(dp) :: dx, x(c%nx)
    integer :: i

    dx = c%length / real(c%nx - 1, dp)
    do i = 1, c%nx
      x(i) = real(i - 1, dp) * dx
    end do

    print '(a)', '막대 열 전달 시뮬레이션'
    print '(a,i0,a,f4.2,a,f5.1,a)', '격자점 ', c%nx, '개, 간격 ', dx, &
          ' m, 시간 간격 ', c%dt, ' s'
    print '(a,f5.3,a)', '안정수 r = ', r, ' (한계 0.500)'
    print '(a)', ''
    print '(a8,*(a4,f4.2))', 't(s)', ('  x=', x(i), i = 1, c%nx)
  end subroutine print_header

  subroutine print_row(t, u)
    real(dp), intent(in) :: t, u(:)
    print '(f8.1,*(f8.2))', t, u
  end subroutine print_row

  subroutine print_obs_summary(first_hour, temp, rain)
    integer, intent(in) :: first_hour
    real(dp), intent(in) :: temp(:), rain(:)
    real(dp) :: mean
    integer :: imax, imin

    mean = sum(temp) / real(size(temp), dp)
    imax = maxloc(temp, dim=1)
    imin = minloc(temp, dim=1)

    print '(a,i0,a,i0,a)', '관측 ', size(temp), '시간 (', first_hour, '시 시작)'
    print '(a,f5.2,a)', '평균 기온 : ', mean, ' C'
    print '(a,f5.2,a,i0,a)', '최고 기온 : ', temp(imax), ' C (', &
          first_hour + imax - 1, '시)'
    print '(a,f5.2,a,i0,a)', '최저 기온 : ', temp(imin), ' C (', &
          first_hour + imin - 1, '시)'
    print '(a,f4.1,a,i0,a)', '총 강수량 : ', sum(rain), ' mm (비 온 시간 ', &
          count(rain > 0.0_dp), '시간)'
  end subroutine print_obs_summary

end module report_mod

main.f90

program main
  use config_mod, only: dp, sim_config, default_config
  use heat_mod, only: stability_number, init_rod, step_explicit
  use report_mod, only: print_header, print_row, print_obs_summary
  implicit none

  real(dp), parameter :: temp(12) = [12.5_dp, 13.0_dp, 14.5_dp, 16.0_dp, &
       18.5_dp, 20.0_dp, 21.5_dp, 22.0_dp, 21.0_dp, 19.5_dp, 17.0_dp, 15.5_dp]
  real(dp), parameter :: rain(12) = [0.0_dp, 0.0_dp, 0.5_dp, 1.0_dp, &
       0.0_dp, 0.0_dp, 0.0_dp, 2.5_dp, 4.0_dp, 0.5_dp, 0.0_dp, 0.0_dp]

  type(sim_config) :: cfg
  real(dp), allocatable :: u(:)
  real(dp) :: r
  integer :: step

  cfg = default_config()

  r = stability_number(cfg)
  if (r > 0.5_dp) error stop '안정 조건 위반: r 이 0.5 를 넘는다'

  allocate (u(cfg%nx))
  call init_rod(cfg, u)

  call print_header(cfg, r)
  call print_row(0.0_dp, u)
  do step = 1, cfg%nsteps
    call step_explicit(r, u)
    if (mod(step, cfg%every) == 0) call print_row(real(step, dp) * cfg%dt, u)
  end do

  print '(a)', ''
  call print_obs_summary(6, temp, rain)
end program main

줄별 해설

config_mod.f90

dp는 15자리 이상을 보장하는 배정밀도 실수 종류다. 다른 모듈이 use config_mod, only: dp로 가져다 쓰므로, 정밀도를 바꾸고 싶으면 이 한 줄만 고치면 된다. sim_config는 설정값 아홉 개를 담는 파생 타입이다. default_config는 이 타입의 값을 만들어 돌려주는 함수이고, 설정을 코드 한곳에 모아 둔다. nsteps = 9, every = 3이므로 3, 6, 9단계에서 표에 한 줄씩 쓴다.

heat_mod.f90

stability_number는 Δx를 구한 뒤 r = α·Δt/Δx²를 돌려준다. real(c%nx - 1, dp)로 정수를 실수로 바꾼 뒤 나누는 점에 주의한다. init_rod는 모든 점을 처음 온도로 채운 뒤 양 끝만 경계값으로 덮어쓴다. 인수 u는 intent(out)이고 모양을 괄호 (:)로 받으므로, 호출하는 쪽의 배열 길이가 그대로 쓰인다. step_explicit의 갱신식은 앞에서 본 식을 배열 구간으로 옮긴 것이다. u(1:n-2)는 왼쪽 이웃, u(3:n)은 오른쪽 이웃을 한꺼번에 가리킨다.

report_mod.f90

print_header는 먼저 각 점의 위치 x(i)를 구한다. 서식 '(a8,*(a4,f4.2))'에서 별표 괄호는 안쪽 묶음을 출력 목록이 끝날 때까지 반복한다. 격자점 수가 바뀌어도 서식을 고칠 필요가 없다. 출력 목록의 (' x=', x(i), i = 1, c%nx)는 문자열과 실수 한 쌍을 점마다 되풀이한다. 각 칸의 너비는 4 + 4 = 8이므로 데이터 줄의 f8.2와 열이 맞는다.

print_row의 서식 '(f8.1,*(f8.2))'은 시각을 소수 한 자리로, 온도를 소수 두 자리로 쓴다. 자릿수를 고정했으므로 출력이 실행할 때마다 같다. print_obs_summary에서 maxloc(temp, dim=1)은 최댓값의 위치를 정수 하나로 돌려준다. dim=1을 쓰지 않으면 길이 1짜리 배열이 나온다. 시각은 first_hour + imax - 1로 바꾼다. 첫 번째 원소가 6시이므로 8번째 원소는 13시다.

main.f90

temp와 rain은 값이 변하지 않는 상수 배열이다. use 문마다 only로 필요한 이름만 가져오면 어느 이름이 어느 모듈에서 왔는지 한눈에 보인다. 설정을 만든 뒤 곧바로 stability_number로 r을 구하고, 0.5를 넘으면 error stop으로 끝낸다. 그다음 allocate로 점 수만큼 배열을 잡고 초기화한 뒤 머리글과 0초의 행을 쓴다. 반복문은 한 단계씩 갱신하고, mod(step, cfg%every) == 0일 때만 줄을 쓴다. 마지막에 빈 줄 하나를 사이에 두고 관측 요약을 출력한다.

실행 결과

네 파일을 한 번에 컴파일하고 실행한다. 경고는 나오지 않는다.

$ gfortran -std=f2018 -Wall -o heat config_mod.f90 heat_mod.f90 report_mod.f90 main.f90
$ ./heat
막대 열 전달 시뮬레이션
격자점 6개, 간격 0.20 m, 시간 간격 100.0 s
안정수 r = 0.250 (한계 0.500)

    t(s)  x=0.00  x=0.20  x=0.40  x=0.60  x=0.80  x=1.00
     0.0  100.00   20.00   20.00   20.00   20.00   20.00
   300.0  100.00   56.25   30.00   21.25   20.00   20.00
   600.0  100.00   66.48   41.35   27.38   21.78   20.00
   900.0  100.00   71.81   48.74   33.31   24.73   20.00

관측 12시간 (6시 시작)
평균 기온 : 17.58 C
최고 기온 : 22.00 C (13시)
최저 기온 : 12.50 C (6시)
총 강수량 :  8.5 mm (비 온 시간 5시간)

표를 읽는 방법은 간단하다. 가로로 보면 한 시각의 막대 온도 분포이고, 세로로 보면 한 지점의 시간에 따른 변화다. 0초에는 왼쪽 끝만 100도이고, 900초가 되면 열이 오른쪽 끝 바로 앞(x = 0.80)까지 번져 24.73도가 된다. 오른쪽 끝은 20도로 고정되어 있다. 첫 3단계에서 x = 0.20 지점은 20도에서 56.25도로 크게 오르지만 다음 3단계에서는 약 10도 오르는 데 그친다. 이웃과의 온도 차가 줄수록 변화가 느려지는 열 방정식의 성질이 표에 나타난다.

실무에서 자주 틀리는 것

반복문으로 제자리 갱신하기

배열 대입을 점마다 반복문으로 풀어 쓰면 결과가 달라진다. 아래 코드에서 u(i-1)은 이미 새 값으로 바뀐 뒤다.

! 틀린 코드
do i = 2, n - 1
  u(i) = u(i) + r * (u(i-1) - 2.0_dp * u(i) + u(i+1))
end do

왼쪽 열이 오른쪽으로 한 단계에 여러 칸씩 번지는 것처럼 보이고, 열 방정식과 다른 식을 푸는 셈이 된다. 컴파일러는 아무 경고도 하지 않는다. 옛 값을 복사해 두고 쓰거나, 본문처럼 배열 구간 대입으로 쓴다.

! 고친 코드
u(2:n-1) = u(2:n-1) + r * (u(1:n-2) - 2.0_dp * u(2:n-1) + u(3:n))

! 반복문을 쓰려면 옛 값 배열을 따로 둔다
old = u
do i = 2, n - 1
  u(i) = old(i) + r * (old(i-1) - 2.0_dp * old(i) + old(i+1))
end do

안정 조건을 검사하지 않기

정밀하게 보려고 격자를 촘촘히 하면서 Δt는 그대로 두는 경우가 많다. nx를 11로 바꾸면 Δx가 0.1이 되어 r이 1.0이 된다.

! 틀린 코드: 검사 없이 바로 반복
r = stability_number(cfg)
call init_rod(cfg, u)
do step = 1, cfg%nsteps
  call step_explicit(r, u)
end do

이 경우 프로그램은 오류 없이 끝나지만 표의 값이 부호를 바꾸며 커진다. 실행이 끝나는 것과 결과가 맞는 것은 별개다. 반복문 앞에서 검사한다.

! 고친 코드
r = stability_number(cfg)
if (r > 0.5_dp) error stop '안정 조건 위반: r 이 0.5 를 넘는다'

정수 나눗셈으로 간격 구하기

막대 길이를 1 m로 외워 두고 리터럴을 직접 쓰면 앞서 배운 정수 나눗셈의 함정에 빠진다.

! 틀린 코드
dx = 1 / (nx - 1)          ! 1/5 가 정수 나눗셈이라 0 이 된다

dx가 0이면 r은 0으로 나눈 값이 되어 검사도 의미를 잃는다. 분자를 실수로 두고 종류까지 맞춘다.

! 고친 코드
dx = c%length / real(c%nx - 1, dp)

컴파일 순서를 거꾸로 적기

파일 이름을 알파벳 순이나 마음대로 적으면 main.f90이 모듈보다 먼저 컴파일된다.

! 틀린 명령
gfortran -std=f2018 -Wall -o heat main.f90 config_mod.f90 heat_mod.f90 report_mod.f90

main.f90의 use 문에서 모듈 파일을 열 수 없다는 오류가 나며 멈춘다. 쓰이는 파일부터 적는다.

! 고친 명령
gfortran -std=f2018 -Wall -o heat config_mod.f90 heat_mod.f90 report_mod.f90 main.f90

한눈에 보기

이 장의 핵심 식과 코드
항목식 또는 코드역할
안정수r = α·Δt/Δx²한 단계에서 이웃 영향이 얼마나 퍼지는지
갱신식u(2:n-1) = u(2:n-1) + r*(…)모든 내부 점을 옛 값으로 동시에 갱신
안정 조건r <= 0.5어기면 값이 진동하며 커짐
사전 검사error stop잘못된 설정으로 계산을 시작하지 않음
표 서식'(f8.1,*(f8.2))'점 수와 무관하게 자릿수 고정
컴파일 순서모듈 파일 먼저, main.f90 마지막.mod 파일이 먼저 만들어지게 함

연습 문제

  1. 설정을 바꾸지 않고 시간 간격만 조정해서 r을 0.4로 만들려면 Δt를 몇 초로 해야 하는가.
  2. nx를 11로 바꾸고 Δt를 100 s로 두면 r은 얼마인가. 이 격자에서 안정 조건을 지키는 Δt의 최댓값은 얼마인가.
  3. 설정에서 every를 6으로 바꾸면 표에는 어느 시각이 출력되는가.
  4. print_obs_summary에 일교차(최고 기온 − 최저 기온)를 출력하는 줄을 한 줄 추가하라. 이 자료에서 값은 얼마인가.

정답과 해설

  1. Δt = r·Δx²/α = 0.4 × 0.04 / 1.0e-4 = 160 s이다. 설정에서 c%dt = 160.0_dp로 바꾸면 된다. 160 s는 한계값 0.5에 대응하는 Δt인 200 s보다 작으므로 안정하다.
  2. Δx = 1.0 / 10 = 0.1이므로 Δx² = 0.01이다. r = 1.0e-4 × 100 / 0.01 = 1.0으로 0.5를 넘어 불안정하다. r = 0.5가 되는 Δt는 0.5 × 0.01 / 1.0e-4 = 50 s이므로, 이 격자에서는 Δt를 50 s 이하로 잡아야 한다. 격자를 두 배 촘촘히 하면 Δt를 4분의 1(100 → 25 s)로 줄인다는 앞의 규칙과도 맞는다. 실제로는 한계값 50 s의 절반인 25 s를 쓰면 이 장의 r = 0.25와 같아진다.
  3. nsteps가 9이므로 6의 배수인 단계는 6단계 하나뿐이다. 따라서 0.0초 행과 600.0초 행 두 줄이 출력되고, 900초 행은 나오지 않는다. 마지막 단계의 상태까지 보고 싶으면 nsteps를 every의 배수로 맞춘다.
  4. 다음 한 줄을 요약 서브루틴 끝에 넣는다.
    print '(a,f5.2,a)', '일교차 : ', maxval(temp) - minval(temp), ' C'
    출력은 일교차 : 9.50 C이다. 최고 22.0과 최저 12.5의 차가 9.5이며, f5.2는 너비 5에 "9.50"이 앞 공백 하나와 함께 들어간다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.