LCOV - code coverage report
Current view: top level - star/private - pgstar_kipp_residuals.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 209 0
Test Date: 2026-11-11 22:04:34 Functions: 0.0 % 5 0

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 2010-2019  The MESA Team
       4              : !
       5              : !   This program is free software: you can redistribute it and/or modify
       6              : !   it under the terms of the GNU Lesser General Public License
       7              : !   as published by the Free Software Foundation,
       8              : !   either version 3 of the License, or (at your option) any later version.
       9              : !
      10              : !   This program is distributed in the hope that it will be useful,
      11              : !   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              : !   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      13              : !   See the GNU Lesser General Public License for more details.
      14              : !
      15              : !   You should have received a copy of the GNU Lesser General Public License
      16              : !   along with this program. If not, see <https://www.gnu.org/licenses/>.
      17              : !
      18              : ! ***********************************************************************
      19              : 
      20              : module pgstar_kipp_residuals
      21              : 
      22              :    use const_def, only: dp, Msun, Rsun
      23              :    use star_private_def
      24              :    use star_pgstar
      25              :    use pgstar_colors
      26              : 
      27              :    implicit none
      28              : 
      29              :    private
      30              :    public :: Kipp_residuals_plot, do_Kipp_residuals_plot
      31              : 
      32              :    real(dp), parameter :: resid_floor = 1d-20
      33              : 
      34              :    ! Grid points with no data (e.g, because of mass loss)
      35              :    ! are not drawn and show the background color.
      36              :    ! The upper half of pgstar's own colormap (pgstar_colors), which
      37              :    ! runs from near-white to pure red. These indices are set once per device
      38              :    ! by set_device_colors, so no color representation is changed here (safe
      39              :    ! for PNG and other file devices). Small residuals -> white, large -> red.
      40              :    ! Grid points with no data (e.g, because of mass loss)
      41              :    ! are not drawn and show the background color.
      42              :    integer, parameter :: c1 = colormap_offset + (colormap_length + 1)/2
      43              :    integer, parameter :: c2 = colormap_offset + colormap_length
      44              : 
      45              : contains
      46              : 
      47              :    ! Kippenhahn-style log10(max(|res|)) across all equations for a
      48              :    ! given space-time location
      49            0 :    subroutine Kipp_residuals_plot(id, device_id, ierr)
      50              :       integer, intent(in)  :: id, device_id
      51              :       integer, intent(out) :: ierr
      52              : 
      53              :       type (star_info), pointer :: s
      54              : 
      55              :       ierr = 0
      56            0 :       call get_star_ptr(id, s, ierr)
      57            0 :       if (ierr /= 0) return
      58              : 
      59            0 :       call pgslct(device_id)
      60            0 :       call pgbbuf()
      61            0 :       call pgeras()
      62              : 
      63              :       call do_Kipp_residuals_plot(s, id, &
      64              :          s% pg% Kipp_residuals_xleft, s% pg% Kipp_residuals_xright, &
      65              :          s% pg% Kipp_residuals_ybot, s% pg% Kipp_residuals_ytop, .false., &
      66              :          s% pg% Kipp_residuals_title, s% pg% Kipp_residuals_txt_scale, &
      67            0 :          s% pg% Kipp_residuals_max_width, s% pg% Kipp_residuals_yaxis_name, ierr)
      68            0 :       if (ierr /= 0) return
      69              : 
      70            0 :       call pgebuf()
      71              : 
      72              :    end subroutine Kipp_residuals_plot
      73              : 
      74              : 
      75            0 :    subroutine do_Kipp_residuals_plot(s, id, &
      76              :          win_xleft, win_xright, win_ybot, win_ytop, subplot, title, txt_scale, &
      77              :          max_width, yaxis_name, ierr)
      78              :       type (star_info), pointer :: s
      79              :       integer, intent(in)  :: id, max_width
      80              :       real, intent(in) :: win_xleft, win_xright, win_ybot, win_ytop, txt_scale
      81              :       logical, intent(in) :: subplot
      82              :       character(len=*), intent(in) :: title, yaxis_name
      83              :       integer, intent(out) :: ierr
      84              : 
      85              :       integer :: k, neq, initial_width, n, x_size, y_size, x_new
      86              :       integer, save :: init_model
      87              : 
      88              :       real, allocatable, dimension(:, :), save :: nr_resid_buf, nr_ycoord_buf
      89              :       integer, allocatable, dimension(:), save :: nr_model_buf, nr_zone_buf
      90              : 
      91            0 :       real, allocatable :: tmp_resid_buf(:, :), tmp_ycoord_buf(:, :)
      92            0 :       integer, allocatable :: tmp_model_buf(:), tmp_zone_buf(:)
      93              : 
      94            0 :       ierr = 0
      95            0 :       neq = size(s% equ, 1)  ! number of equations per cell
      96              : 
      97            0 :       if (.not. allocated(nr_resid_buf)) then
      98            0 :          if (max_width < 0) then
      99              :             initial_width = 10
     100              :          else
     101            0 :             initial_width = max_width
     102              :          end if
     103              :          ! 1.5 times the current nz; grown below if remeshing increases nz
     104            0 :          allocate(nr_resid_buf(initial_width, int(1.5 * s% nz)))
     105            0 :          allocate(nr_ycoord_buf(initial_width, int(1.5 * s% nz)))
     106            0 :          allocate(nr_model_buf(initial_width))
     107            0 :          allocate(nr_zone_buf(initial_width))
     108            0 :          nr_resid_buf = 0.0
     109            0 :          nr_ycoord_buf = 0.0
     110            0 :          nr_model_buf = 0
     111            0 :          nr_zone_buf = 0   ! 0 zones = empty column, skipped when plotting
     112              : 
     113            0 :          init_model = s% model_number - 1
     114            0 :       else if (s% nz > 0.95 * size(nr_resid_buf, 2)) then
     115              :          ! remeshing increased nz beyond 95% of the current array size
     116            0 :          x_size = size(nr_resid_buf, 1)
     117            0 :          y_size = size(nr_resid_buf, 2)
     118            0 :          call move_alloc(nr_resid_buf, tmp_resid_buf)
     119            0 :          call move_alloc(nr_ycoord_buf, tmp_ycoord_buf)
     120              : 
     121            0 :          allocate(nr_resid_buf(x_size, 5 * s% nz))
     122            0 :          allocate(nr_ycoord_buf(x_size, 5 * s% nz))
     123            0 :          nr_resid_buf = 0.0
     124            0 :          nr_ycoord_buf = 0.0
     125              : 
     126            0 :          nr_resid_buf(:, 1:y_size) = tmp_resid_buf(:, 1:y_size)
     127            0 :          nr_ycoord_buf(:, 1:y_size) = tmp_ycoord_buf(:, 1:y_size)
     128              : 
     129            0 :          deallocate(tmp_resid_buf, tmp_ycoord_buf)
     130              :       end if
     131              : 
     132            0 :       if (max_width < 0) then
     133            0 :          n = s% model_number - init_model
     134              :       else
     135            0 :          n = min(s% model_number - init_model, max_width)
     136              :       end if
     137            0 :       if (n < 1) return
     138              : 
     139              :       ! grow dimension
     140            0 :       if (n > size(nr_resid_buf, 1)) then
     141            0 :          x_size = size(nr_resid_buf, 1)
     142            0 :          y_size = size(nr_resid_buf, 2)
     143            0 :          x_new = max(2 * x_size, n)
     144              : 
     145            0 :          call move_alloc(nr_resid_buf, tmp_resid_buf)
     146            0 :          call move_alloc(nr_ycoord_buf, tmp_ycoord_buf)
     147            0 :          call move_alloc(nr_zone_buf, tmp_zone_buf)
     148            0 :          call move_alloc(nr_model_buf, tmp_model_buf)
     149              : 
     150            0 :          allocate(nr_resid_buf(x_new, y_size))
     151            0 :          allocate(nr_ycoord_buf(x_new, y_size))
     152            0 :          allocate(nr_zone_buf(x_new))
     153            0 :          allocate(nr_model_buf(x_new))
     154              : 
     155            0 :          nr_resid_buf = 0.0
     156            0 :          nr_ycoord_buf = 0.0
     157            0 :          nr_zone_buf = 0
     158            0 :          nr_model_buf = 0
     159              : 
     160            0 :          nr_resid_buf(1:x_size, :) = tmp_resid_buf(1:x_size, :)
     161            0 :          nr_ycoord_buf(1:x_size, :) = tmp_ycoord_buf(1:x_size, :)
     162            0 :          nr_zone_buf(1:x_size) = tmp_zone_buf(1:x_size)
     163            0 :          nr_model_buf(1:x_size) = tmp_model_buf(1:x_size)
     164              : 
     165            0 :          deallocate(tmp_resid_buf, tmp_ycoord_buf, tmp_zone_buf, tmp_model_buf)
     166              :       end if
     167              : 
     168            0 :       if (max_width > 1 .and. s% model_number - init_model > max_width) then
     169            0 :          nr_model_buf(1:max_width-1) = nr_model_buf(2:max_width)
     170            0 :          nr_resid_buf(1:max_width-1, :) = nr_resid_buf(2:max_width, :)
     171            0 :          nr_ycoord_buf(1:max_width-1, :) = nr_ycoord_buf(2:max_width, :)
     172            0 :          nr_zone_buf(1:max_width-1) = nr_zone_buf(2:max_width)
     173              :       end if
     174              : 
     175            0 :       nr_model_buf(n) = s% model_number
     176            0 :       nr_zone_buf(n) = s% nz
     177              : 
     178              :       ! store the current model; all y coordinates are cell centered
     179            0 :       do k = 1, s% nz
     180              :          nr_resid_buf(n, k) = real(log10(max(resid_floor, &
     181            0 :             maxval(abs(s% equ(1:neq, k))))))
     182            0 :          select case (trim(yaxis_name))
     183              :          case ('logR')
     184            0 :             nr_ycoord_buf(n, k) = real(log10(max(1d-99, s% rmid(k)) / Rsun))
     185              :          case ('tau')
     186            0 :             nr_ycoord_buf(n, k) = real(log10(max(1d-99, s% tau(k))))
     187              :          case default  ! 'mass'
     188            0 :             nr_ycoord_buf(n, k) = real((s% m(k) - 0.5d0 * s% dm(k)) / Msun)
     189              :          end select
     190              :       end do
     191              : 
     192            0 :       call pgsave
     193            0 :       call pgsci(0)                       ! color index 0 = background
     194            0 :       call pgsvp(win_xleft, win_xright, win_ybot, win_ytop)
     195            0 :       call pgrect(0.0, 1.0, 0.0, 1.0)     ! pseudo erase
     196              :       call render(n, nr_model_buf, nr_zone_buf, nr_resid_buf, nr_ycoord_buf, &
     197              :          max(2, s% nz), yaxis_name, title, txt_scale, &
     198            0 :          s% pg% Kipp_residuals_min_residual, s% pg% Kipp_residuals_max_residual)
     199            0 :       call pgunsa
     200              : 
     201              :    end subroutine do_Kipp_residuals_plot
     202              : 
     203              : 
     204            0 :    subroutine render(nx, model_buf, zone_buf, resid_buf, ycoord_buf, &
     205              :          ny, yaxis_name, title, txt_scale, cbar_min, cbar_max)
     206              :       integer, intent(in) :: nx, ny
     207              :       integer, intent(in) :: model_buf(:), zone_buf(:)
     208              :       real, intent(in) :: resid_buf(:, :), ycoord_buf(:, :)
     209              :       character(len=*), intent(in) :: yaxis_name, title
     210              :       real, intent(in) :: txt_scale
     211              :       real, intent(in) :: cbar_min, cbar_max
     212            0 :       real, allocatable :: col(:, :), coord_grid(:)
     213              :       real :: tr(6), fg, bg, missing, xlo, xhi, xl, xr
     214              :       real :: clo, chi, dcoord, resid_hi, resid_lo
     215              :       integer :: i, j, j1, j2, nz_i, ip, cir_lo_save, cir_hi_save
     216              :       logical :: y_reverse, have_data
     217              : 
     218              :       ! --- data ranges over all stored columns ---
     219            0 :       have_data = .false.
     220            0 :       chi = -huge(1.0)
     221            0 :       clo = huge(1.0)
     222            0 :       resid_hi = -huge(1.0); resid_lo = huge(1.0)
     223            0 :       do i = 1, nx
     224            0 :          nz_i = zone_buf(i)
     225            0 :          if (nz_i < 1) cycle
     226            0 :          have_data = .true.
     227            0 :          chi = max(chi, maxval(ycoord_buf(i, 1:nz_i)))
     228            0 :          clo = min(clo, minval(ycoord_buf(i, 1:nz_i)))
     229            0 :          resid_hi = max(resid_hi, maxval(resid_buf(i, 1:nz_i)))
     230            0 :          resid_lo = min(resid_lo, minval(resid_buf(i, 1:nz_i)))
     231              :       end do
     232            0 :       if (.not. have_data) return
     233            0 :       if (chi <= clo) chi = clo + 1.0
     234              : 
     235            0 :       y_reverse = (trim(yaxis_name) == 'tau')
     236              : 
     237              :       ! uniform y grid, ascending with j; axis reversal is done in pgswin
     238            0 :       allocate(coord_grid(ny), col(1, ny))
     239            0 :       dcoord = (chi - clo) / real(ny - 1)
     240            0 :       do j = 1, ny
     241            0 :          coord_grid(j) = clo + real(j - 1) * dcoord
     242              :       end do
     243              : 
     244              :       ! --- color scale ---
     245            0 :       if (cbar_max /= -101.0) then
     246            0 :          fg = cbar_max
     247              :       else
     248            0 :          fg = resid_hi
     249              :       end if
     250            0 :       if (cbar_min /= -101.0) then
     251            0 :          bg = cbar_min
     252              :       else
     253            0 :          bg = resid_lo
     254              :       end if
     255            0 :       if (fg - bg < 1.0) then
     256            0 :          if (cbar_min /= -101.0 .and. cbar_max == -101.0) then
     257            0 :             fg = bg + 1.0
     258              :          else
     259            0 :             bg = fg - 1.0
     260              :          end if
     261              :       end if
     262            0 :       missing = bg - 1.0   ! marker only, never drawn
     263              : 
     264            0 :       call pgqcir(cir_lo_save, cir_hi_save)
     265            0 :       call pgscir(c1, c2)
     266              : 
     267              :       ! --- x range from stored model numbers ---
     268            0 :       xlo = huge(1.0); xhi = -huge(1.0)
     269            0 :       ip = 0
     270            0 :       do i = 1, nx
     271            0 :          if (zone_buf(i) < 1) cycle
     272            0 :          call column_edges(i, ip, nx, model_buf, zone_buf, xl, xr)
     273            0 :          xlo = min(xlo, xl)
     274            0 :          xhi = max(xhi, xr)
     275            0 :          ip = i
     276              :       end do
     277              : 
     278            0 :       if (.not. y_reverse) then
     279            0 :          call pgswin(xlo, xhi, clo - 0.5*dcoord, chi + 0.5*dcoord)
     280              :       else
     281            0 :          call pgswin(xlo, xhi, chi + 0.5*dcoord, clo - 0.5*dcoord)
     282              :       end if
     283              : 
     284              :       ! fill the plot area with grey first: points without data (e.g. above
     285              :       ! the surface after mass loss) stay grey, distinct from white (small
     286              :       ! residuals) and from the background, whether black or white
     287            0 :       call pgsci(clr_Gray)
     288            0 :       call pgsfs(1)
     289            0 :       call pgrect(xlo, xhi, clo - 0.5*dcoord, chi + 0.5*dcoord)
     290              : 
     291              :       ! --- one pgimag call per stored model, placed at its model number ---
     292            0 :       tr(3) = 0.0
     293            0 :       tr(4) = clo - dcoord   ! j = 1 -> clo
     294            0 :       tr(5) = 0.0
     295            0 :       tr(6) = dcoord
     296            0 :       ip = 0
     297            0 :       do i = 1, nx
     298            0 :          nz_i = zone_buf(i)
     299            0 :          if (nz_i < 1) cycle
     300            0 :          call column_edges(i, ip, nx, model_buf, zone_buf, xl, xr)
     301            0 :          ip = i
     302              :          call interp_column(nz_i, ycoord_buf(i, 1:nz_i), resid_buf(i, 1:nz_i), &
     303            0 :             ny, coord_grid, dcoord, bg, fg, missing, col(1, :))
     304            0 :          tr(2) = xr - xl
     305            0 :          tr(1) = 0.5*(xl + xr) - tr(2)   ! array index 1 -> column center
     306              :          ! the column coordinate is monotone, so missing points can only be
     307              :          ! at the ends; draw just the contiguous range with data
     308            0 :          j1 = 1
     309            0 :          do while (j1 <= ny)
     310            0 :             if (col(1, j1) > missing) exit
     311            0 :             j1 = j1 + 1
     312              :          end do
     313            0 :          j2 = ny
     314            0 :          do while (j2 >= j1)
     315            0 :             if (col(1, j2) > missing) exit
     316            0 :             j2 = j2 - 1
     317              :          end do
     318            0 :          if (j1 > j2) cycle
     319            0 :          call pgimag(col, 1, ny, 1, 1, j1, j2, bg, fg, tr)
     320              :       end do
     321              : 
     322              :       ! --- axes and labels ---
     323            0 :       call pgsci(clr_Foreground)
     324            0 :       call pgsch(txt_scale)
     325            0 :       call pgbox('BCNST', 0.0, 0, 'BCNST', 0.0, 0)
     326            0 :       call pgmtxt('B', 2.5, 0.5, 0.5, 'Model Number')
     327            0 :       select case (trim(yaxis_name))
     328              :       case ('logR')
     329            0 :          call pgmtxt('L', 3.0, 0.5, 0.5, 'log(R/R\d\(2281)\u)')
     330              :       case ('tau')
     331            0 :          call pgmtxt('L', 3.0, 0.5, 0.5, 'log(\gt)')
     332              :       case default
     333            0 :          call pgmtxt('L', 3.0, 0.5, 0.5, 'm/M\d\(2281)\u')
     334              :       end select
     335            0 :       call pgmtxt('T', 1.0, 0.5, 0.5, trim(title))
     336              : 
     337              :       ! --- color wedge ---
     338            0 :       call pgwedg('RI', 0.5, 3.0, bg, fg, 'log\d10\u max |res|')
     339              : 
     340              :       ! restore the index range; no color representations were modified
     341            0 :       call pgscir(cir_lo_save, cir_hi_save)
     342              : 
     343            0 :       deallocate(col, coord_grid)
     344              : 
     345              :    end subroutine render
     346              : 
     347              : 
     348              :    ! x extent of column i: halfway to the neighboring non-empty columns
     349            0 :    subroutine column_edges(i, iprev, nx, model_buf, zone_buf, xl, xr)
     350              :       integer, intent(in) :: i, iprev, nx
     351              :       integer, intent(in) :: model_buf(:), zone_buf(:)
     352              :       real, intent(out) :: xl, xr
     353              :       integer :: inext, j
     354              :       real :: xc
     355              : 
     356            0 :       xc = real(model_buf(i))
     357            0 :       inext = 0
     358            0 :       do j = i + 1, nx
     359            0 :          if (zone_buf(j) > 0) then
     360              :             inext = j
     361              :             exit
     362              :          end if
     363              :       end do
     364              : 
     365            0 :       if (iprev > 0) then
     366            0 :          xl = 0.5 * (real(model_buf(iprev)) + xc)
     367            0 :       else if (inext > 0) then
     368            0 :          xl = xc - 0.5 * (real(model_buf(inext)) - xc)
     369              :       else
     370            0 :          xl = xc - 0.5
     371              :       end if
     372            0 :       if (inext > 0) then
     373            0 :          xr = 0.5 * (real(model_buf(inext)) + xc)
     374            0 :       else if (iprev > 0) then
     375            0 :          xr = xc + 0.5 * (xc - real(model_buf(iprev)))
     376              :       else
     377            0 :          xr = xc + 0.5
     378              :       end if
     379            0 :    end subroutine column_edges
     380              : 
     381            0 :    subroutine interp_column(m, ycol, rcol, ny, coord_grid, dcoord, bg, fg, missing, out)
     382              :       integer, intent(in) :: m, ny
     383              :       real, intent(in) :: ycol(m), rcol(m), coord_grid(ny), dcoord, bg, fg, missing
     384              :       real, intent(out) :: out(ny)
     385            0 :       real :: yc(m), rv(m), y, v, dy
     386              :       integer :: j, kk
     387              : 
     388            0 :       if (ycol(1) <= ycol(m)) then
     389            0 :          yc = ycol; rv = rcol
     390              :       else
     391            0 :          yc = ycol(m:1:-1); rv = rcol(m:1:-1)
     392              :       end if
     393              : 
     394              :       kk = 1
     395            0 :       do j = 1, ny
     396            0 :          y = coord_grid(j)
     397            0 :          if (y < yc(1)) then
     398              :             ! allow up to one grid step beyond the outermost cell center
     399            0 :             if (y < yc(1) - dcoord) then
     400            0 :                out(j) = missing
     401            0 :                cycle
     402              :             end if
     403            0 :             v = rv(1)
     404            0 :          else if (y > yc(m)) then
     405            0 :             if (y > yc(m) + dcoord) then
     406            0 :                out(j) = missing
     407            0 :                cycle
     408              :             end if
     409            0 :             v = rv(m)
     410            0 :          else if (m == 1) then
     411            0 :             v = rv(1)
     412              :          else
     413            0 :             do while (kk < m - 1)
     414            0 :                if (yc(kk + 1) >= y) exit
     415            0 :                kk = kk + 1
     416              :             end do
     417            0 :             dy = yc(kk + 1) - yc(kk)
     418            0 :             if (dy > 0.0) then
     419            0 :                v = rv(kk) + (y - yc(kk)) / dy * (rv(kk + 1) - rv(kk))
     420              :             else
     421            0 :                v = 0.5 * (rv(kk) + rv(kk + 1))
     422              :             end if
     423              :          end if
     424            0 :          out(j) = min(fg, max(bg, v))
     425              :       end do
     426            0 :    end subroutine interp_column
     427              : 
     428              : end module pgstar_kipp_residuals
        

Generated by: LCOV version 2.0-1