File: spray.f08

package info (click to toggle)
munipack 0.6.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 33,104 kB
  • sloc: cpp: 29,677; sh: 4,909; f90: 2,872; makefile: 278; python: 140; xml: 72; awk: 12
file content (415 lines) | stat: -rw-r--r-- 11,040 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
!
!  Spreaded profiles
!
!
!  Copyright © 2016-2022 F.Hroch (hroch@physics.muni.cz)
!
!  This file is part of Munipack.
!
!  Munipack is free software: you can redistribute it and/or modify
!  it under the terms of the GNU General Public License as published by
!  the Free Software Foundation, either version 3 of the License, or
!  (at your option) any later version.
!
!  Munipack is distributed in the hope that it will be useful,
!  but WITHOUT ANY WARRANTY; without even the implied warranty of
!  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
!  GNU General Public License for more details.
!
!  You should have received a copy of the GNU General Public License
!  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.
!


module spray

  implicit none

  integer, parameter, private :: dbl = selected_real_kind(15)
  integer, parameter, private :: defzoom = 2**4
  real(dbl), parameter, private :: rad = 57.295779513082322865_dbl
  real(dbl), parameter, private :: sqrt2 = sqrt(2.0)

  type SprayType
     integer :: zoom = defzoom
     integer :: nbox
     real(dbl), dimension(:,:), allocatable :: psf
   contains
     procedure :: Init, Pixelize, Load
     procedure :: gauss, moffat, seeing, cseeing, fseeing
  end type SprayType

  private :: gauss, moffat, seeing, cseeing, fseeing, fill_gaussian, fill_bessel

contains

  subroutine Init(this,profile,spread,hwhm,eccentricity,inclination, &
       airy,beta)

    class(SprayType) :: this
    character(len=*), intent(in) :: profile, spread
    real(dbl), intent(in) :: hwhm, airy, beta, eccentricity,inclination

    if( profile == 'GAUSS' ) then

       call this%gauss(hwhm,eccentricity,inclination)

    else if( profile == 'MOFFAT' ) then

       call this%moffat(hwhm,beta,eccentricity,inclination)

    else ! if( profile == 'SEEING' ) then

       if( spread == 'RANDOM' ) then

          call this%seeing(hwhm,airy,eccentricity,inclination)

       else if( spread == 'TEST' ) then

          call this%cseeing(hwhm,airy,eccentricity,inclination)

       else ! if (spread == 'FFT' ) then

          call this%fseeing(hwhm,airy,eccentricity,inclination)

       end if
    end if

    ! normalisation
    this%psf = this%psf * this%zoom**2 / sum(this%psf)

  end subroutine Init


  subroutine seeing(this,hwhm,airy,e,incl)

    ! The seeing is simulated by summing of Airy profiles with random
    ! offsets. The distribution of offsets is Gaussian N(0,hwhm).
    ! This method is very illustrative; it simulates the real process
    ! of an image generation. On the other side, it is slow for many
    ! iterations. It gives an asymmetric and randomly noised images.

    use noise

    integer, parameter :: maxiter = 5000

    class(SprayType) :: this
    real(dbl), intent(in) :: hwhm, airy, e,incl

    real(dbl), dimension(:,:), allocatable :: bessel
    integer :: n,m,i,j,iter,l1,l2,k1,k2
    real(dbl) :: x,y,u,v,a,b,c,s,sig

    this%nbox = nint(13*max(hwhm,1.0)*max(airy,5.0))
    n = this%nbox*this%zoom
    m = n / 2
    allocate(this%psf(-n:n,-n:n),bessel(-m:m,-m:m))

    ! fill-up by Bessel
    call fill_bessel(m,this%zoom*airy,bessel)

    ! spread the profile by random shifts with N(0,s)
    sig = hwhm*this%zoom
    a = sqrt2*sig
    b = a*sqrt(1 - e**2)
    c = cos(incl/rad)
    s = sin(incl/rad)

    this%psf = 0
    do iter = 1, maxiter
       u = gnoise(0.0_dbl,a)
       v = gnoise(0.0_dbl,b)
       x = u*c + v*s
       y =-u*s + v*c
       i = nint(x)
       j = nint(y)

       k1 = i - m
       k2 = i + m
       l1 = j - m
       l2 = j + m
       this%psf(k1:k2,l1:l2) = this%psf(k1:k2,l1:l2) + bessel
    end do

    deallocate(bessel)

  end subroutine seeing


  subroutine cseeing(this,hwhm,airy, e,incl)

    ! The seeing is simulated by convolution of Airy disk function and
    ! gaussian giving spread. The convolution is computed by direct
    ! sum, so by very slow way.

    class(SprayType) :: this
    real(dbl), intent(in) :: hwhm, airy, e,incl

    real(dbl), dimension(:,:), allocatable :: bessel, kernel
    integer :: n,m,i,j,k,l
    real(dbl) :: s

    this%nbox = nint(13*max(hwhm,1.0)*max(airy,5.0))
    n = this%nbox*this%zoom
    m = n / 2
    allocate(this%psf(-n:n,-n:n),bessel(-m:m,-m:m),kernel(-n:n,-n:n))

    ! fill-up the Bessel intensity
    call fill_bessel(m,this%zoom*airy,bessel)

    ! fill kernel
    call fill_gaussian(m,this%zoom*hwhm,e,incl,kernel)

    ! spread profile by direct convolution
    this%psf = 0
    do i = -m,m
       do j = -m,m
          s = 0
          do l = -m,m
             do k = -m,m
                s = s + bessel(k,l)*kernel(i-l,j-k)
             end do
          end do
          this%psf(i,j) = s
       end do
    end do

    deallocate(bessel,kernel)

  end subroutine cseeing

  subroutine fseeing(this,hwhm,airy,e,incl)

    ! The seeing is simulated by convolution of Airy disk function and
    ! gaussian giving spread. The convolution is computed by using
    ! os Fourier transformation. It is most fast and precise method.

    use ftransform

    class(SprayType) :: this
    real(dbl), intent(in) :: hwhm, airy,e,incl

    complex(dbl), dimension(:,:), allocatable :: bessel, kernel, qpsf, &
         zbessel, zkernel, zpsf
    real(dbl), dimension(:,:), allocatable :: rkernel, rbessel
    integer :: n,m,n2

    n = nint(13*max(hwhm,1.0)*max(airy,5.0))
    m = int(log(real(n*this%zoom))/log(2.0) + 1)  ! power o 2 for FFT
    n = 2**m
    this%nbox = n / this%zoom
    n = this%nbox * this%zoom
    n2 = n / 2

    ! fill-up Bessel
    allocate(bessel(n,n),rbessel(-n2:n2,-n2:n2))
    call fill_bessel(n2,this%zoom*airy,rbessel)
    bessel = rbessel(-n2+1:n2,-n2+1:n2)
    deallocate(rbessel)

    ! fill-up Gaussian spread
    allocate(kernel(n,n),rkernel(-n2:n2,-n2:n2))
    call fill_gaussian(n2,this%zoom*hwhm,e,incl,rkernel)
    ! conversion Complex to Real numbers
    kernel = rkernel(-n2+1:n2,-n2+1:n2)
    deallocate(rkernel)

    ! forward FFT
    allocate(zbessel(n,n),zkernel(n,n))
    call ftra(bessel,zbessel,m,1)
    call ftra(kernel,zkernel,m,1)
    deallocate(bessel,kernel)

    ! the convolution
    allocate(zpsf(n,n),qpsf(n,n))
    zpsf = zbessel * zkernel

    ! back FFT
    call ftra(zpsf,qpsf,m,-1)
    deallocate(zbessel,zkernel,zpsf)

    ! fill PSF by re-arranged quarters
    allocate(this%psf(-n2+1:n2-1,-n2+1:n2-1))
    this%psf = 0
    this%psf(    0:n2-1,    0:n2-1) = real(qpsf(1:n2,1:n2))
    this%psf(-n2+1:-1,      0:n2-1) = real(qpsf(n2+1:n-1,1:n2))
    this%psf(    0:n2-1,-n2+1:-1)   = real(qpsf(1:n2,n2+1:n-1))
    this%psf(-n2+1:-1,  -n2+1:-1)   = real(qpsf(n2+1:n-1,n2+1:n-1))

    deallocate(qpsf)

  end subroutine fseeing

  subroutine fill_bessel(m,sig,bessel)

    integer, intent(in) :: m
    real(dbl), intent(in) :: sig
    real(dbl), dimension(-m:,-m:), intent(out) :: bessel

    real(dbl) :: x,y,r
    integer :: i,j

    ! fill-up the top-right quarter of Airy disk
    do i = 0,m
       x = i / sig
       do j = 0,m
          y = j / sig
          ! https://en.wikipedia.org/wiki/Airy_disk
          if( i == 0 .and. j == 0 ) then
             bessel(i,j) = 1
          else
             r = sqrt(x**2 + y**2)
             bessel(i,j) = (bessel_j1(r)/r)**2
          end if
       end do
    end do

    ! fill-up rest of the kernel
    forall( i = -m:0, j = 0:m ) bessel(i,j) = bessel(-i,j)
    forall( i = -m:m, j = -m:-1 ) bessel(i,j) = bessel(i,-j)

  end subroutine fill_bessel


  subroutine fill_gaussian(m,sig,e,incl,kernel)

    integer, intent(in) :: m
    real(dbl), intent(in) :: sig, e, incl
    real(dbl), dimension(-m:,-m:), intent(out) :: kernel

    real(dbl) :: x,y,a,b,c,s
    integer :: i,j

    a = sqrt2*sig
    b = a*sqrt(1 - e**2)
    c = cos(incl/rad)
    s = sin(incl/rad)

    do i = -m, m
       do j = -m, m
          x = ( i*c + j*s) / a
          y = (-i*s + j*c) / b
          kernel(i,j) = exp(-(x**2 + y**2))
       end do
    end do

  end subroutine fill_gaussian

  subroutine gauss(this,hwhm,e,incl)

    class(SprayType) :: this
    real(dbl), intent(in) :: hwhm, e, incl
    integer :: n

    this%nbox = nint(7*max(hwhm,1.0))
    n = this%zoom * this%nbox
    allocate(this%psf(-n:n,-n:n))

    call fill_gaussian(n,this%zoom*hwhm,e,incl,this%psf)

  end subroutine gauss


  subroutine moffat(this, hwhm, beta, e,incl)

    class(SprayType) :: this
    real(dbl), intent(in) :: hwhm, beta, e, incl
    real(dbl) :: a, b, c, s, x, y
    integer :: i,j,n

    this%nbox = nint(30*max(hwhm,1.0))
    n = this%zoom * this%nbox

    allocate(this%psf(-n:n,-n:n))

    a = this%zoom * hwhm
    b = a*sqrt(1 - e**2)
    c = cos(incl/rad)
    s = sin(incl/rad)

    do i = -n, n
       do j = -n, n
          x = ( i*c + j*s) / a
          y = (-i*s + j*c) / b
          this%psf(i,j) = (1 + (x**2 + y**2))**(-beta)
       end do
    end do

  end subroutine moffat


  subroutine Pixelize(this,dx,dy,xpsf,n)

    class(SprayType) :: this
    real(dbl), intent(in) :: dx, dy
    real(dbl), dimension(-n:n,-n:n), intent(out) :: xpsf
    integer, intent(in) :: n
    ! The default lower dimension in subroutine is 1 (allocatable array?),
    ! we pass the dimensions which we needs.

    integer :: x,y,k,l,i,j,k1,k2,l1,l2,m,m2,nmax,kk,ll

    nmax = ubound(this%psf,1)
    x = nint(this%zoom*dx)
    y = nint(this%zoom*dy)
    m = this%zoom
    m2 = m / 2

    xpsf = 0
    do i = -n,n
       k = i*m + x
       k1 = max(k - m2,-nmax)
       k2 = min(k + m2, nmax)
       kk = k2 - k1 + 1
       do j = -n,n
          l = j*m + y
          l1 = max(l - m2,-nmax)
          l2 = min(l + m2, nmax)
          ll = l2 - l1 + 1
          if( kk > 0 .and. ll > 0 ) then
             xpsf(i,j) = sum(this%psf(k1:k2,l1:l2)) / (kk*ll)
          end if
       end do
    end do

  end subroutine Pixelize

  subroutine Load(this,filename)

    use titsio
    use iso_fortran_env

    class(SprayType) :: this
    character(len=*), intent(in) :: filename
    type(fitsfiles) :: fits
    integer :: naxis,status,n,n1,zoom
    integer, dimension(2) :: naxes
    character(len=FLEN_COMMENT) :: comment
    logical :: anynull

    status = 0
    call fits_open_file(fits,filename,FITS_READONLY,status)
    if( status == 0 ) then
       call fits_get_img_dim(fits,naxis,status)
       if( naxis /= 2 ) stop 'spray.Load(): PSF should by 2D image array.'
       call fits_get_image_size(fits,naxes,status)
       if( naxes(1) /= naxes(2) ) stop 'spray.Load(): PSF should be a square.'
       call fits_read_key(fits,'ZOOM',zoom,comment,status)
       if( status /= 0 .or. zoom < 1 ) stop 'spray.Load(): zoom undefined.'

       n = naxes(1) / 2
       n1 = naxes(1) - (n + 1)
       allocate(this%psf(-n:n1,-n:n1))
       this%nbox = n
       this%zoom = zoom

       call fits_read_image(fits,1,0.0_dbl,this%psf,anynull,status)
       call fits_close_file(fits,status)
    end if
    call fits_report_error(error_unit,status)
    if( status /= 0 ) stop 'spray.Load() failed to read a FITS file with PSF.'

  end subroutine Load


end module spray