kipp files¶
Introduction¶
When using kippy with MESA, it is generally optimal for both disk space and file count to instead dump the relevant quantities into Fortran binary over the native ASCII formats of profile files. For this reason, described below are a set of routines to make the .kipp files that kippy expects, and an example/tutorial for their implementation.
This example is also available in the example/ directory of the repository.
Implementation¶
Adding a hook to make .kipp files requires overrides to three routine pointers of subroutine extras_controls src/run_star_extras.f90 of the standard star/work directory. You may already use these hooks for other science. Worry not, because the process is designed to be modular.
Warning
If use include's as below, you will need to rebuild via ./clean single time and ./mk
every time changes are made because the make system is not aware of changes to *.inc files.
subroutine extras_controls(id, ierr)
integer, intent(in) :: id
integer, intent(out) :: ierr
type(star_info), pointer :: s
ierr = 0
call star_ptr(id, s, ierr)
if (ierr /= 0) return
! at LEAST the following three hooks are necessary
s%extras_startup => extras_startup ! used to open the file safely
s%extras_finish_step => extras_finish_step ! used to write the data
s%extras_after_evolve => extras_after_evolve ! used to close the file safely
! you may do whatever you want for your science with other hooks
end subroutine extras_controls
You must then add the relevant variables to the module declaration.
module run_star_extras
use star_lib
use star_def
use const_def
use math_lib
use auto_diff
implicit none
include 'kipp/params.inc' ! <--- this contains kippy variables
Where the file kipp/params.inc can be:
Tip
kippy works best when you output every single model for plotting. This
allows you to check for insufficient resolution in both space and time.
! "kipp" record (for Kippenhahn diagrams via kippy):
! direct stream file -- every cell of every timestep together!
! toggle set via x_integer_ctrl(kipp_idx) = (cadence): <= 0 off (default), n saves every nth step.
! you should really save n = 1 (all) steps for the best rendering.
! you can choose where you want the ctrl index via the ikipp_on variable below
!
! path is chosen from x_character_ctrl(kipp_idx), else <log_directory>/profile.kipp.
! 12 real(dp) columns per cell, see write_kipp_record / open_kipp_record.
! change these for yourself!
integer, parameter :: kipp_ncols = 12
integer, parameter :: kipp_idx = 4 ! the x_integer_ctrl index to access for cadence
! these are used internally, no need to change them
integer :: kipp_unit = -1 ! stream unit, -1 when closed
integer :: kipp_cadence = 0 ! x_integer_ctrl(kipp_idx); <= 0 disables
logical :: kipp_on = .false. ! flag if enabled for this run
The three pointed routines themselves must look like:
subroutine extras_startup(id, restart, ierr)
integer, intent(in) :: id
logical, intent(in) :: restart
integer, intent(out) :: ierr
type(star_info), pointer :: s
ierr = 0
call star_ptr(id, s, ierr)
if (ierr /= 0) return
! your stuff here
! if startup is going well, open the file
call open_kipp_record(s, restart)
end subroutine extras_startup
integer function extras_finish_step(id)
integer, intent(in) :: id
integer :: ierr
type(star_info), pointer :: s
ierr = 0
call star_ptr(id, s, ierr)
if (ierr /= 0) return
extras_finish_step = keep_going
! other stuff here
! best to write at the end of the function
call write_kipp_record(s)
end function extras_finish_step
subroutine extras_after_evolve(id, ierr)
integer, intent(in) :: id
integer, intent(out) :: ierr
type(star_info), pointer :: s
ierr = 0
call star_ptr(id, s, ierr)
if (ierr /= 0) return
! other stuff here
! safely close the file
call close_kipp_record()
end subroutine extras_after_evolve
The three routines themselves to open/write/close the record can be included:
! anywhere in the "contains" declaration
include 'kipp/routines.inc'
! ....
end module run_star_extras
And the file kipp/routines.inc should be:
! kipp files
!
! log a minimal set of vars every timestep for use in plotting the resolution in a
! Kippenhahn diagram (see kippy).
!
! opt-in via x_integer_ctrl(kipp_idx) (cadence): <= 0 off (default), n saves every
! nth step (model_number 1, 1+n, ...).
!
! path from x_character_ctrl(kipp_idx) if set,
! else <log_directory>/profile.kipp. file is a raw stream of real(dp) vars.
!
! dumb read in python with np.fromfile(path).reshape(-1, kipp_ncols).
! we use a one-time header write at at <path>.hdr
!
! columns (all real(dp)):
! 1 model_number 2 star_age(yr) 3 dt(s) 4 zone k
! 5 dm(g) 6 m(g) 7 r(cm) 8 T(K)
! 9 rho(g/cc) 10 mlt_mixing_type 11 eps_net(erg/g/s) 12 L(erg/s)
! col 11 is eps_nuc - non_nuc_neu (i.e., MESA's net_nuclear_energy)
!
! you may add new columns if you need, this requires:
! 1. you change the ncols in the params file to accomodate
! 2. you add the relevant name to the header function below
! 3. you add the relevant calculated quantity to the record function below
subroutine open_kipp_record(s, restart)
type(star_info), pointer :: s
logical, intent(in) :: restart
integer :: ierr, u
character(len=256) :: path
kipp_on = .false.
kipp_unit = -1
kipp_cadence = s%x_integer_ctrl(kipp_idx)
if (kipp_cadence <= 0) return ! feature off (default)
if (len_trim(s%x_character_ctrl(kipp_idx)) > 0) then
path = trim(s%x_character_ctrl(kipp_idx))
else
path = trim(s%log_directory)//'/profile.kipp'
end if
ierr = 0
if (restart) then
! continue the existing file across a photo restart
open (newunit=u, file=trim(path), access='stream', form='unformatted', &
status='old', position='append', action='write', iostat=ierr)
else
! fresh run: truncate and (re)write the column header sidecar
open (newunit=u, file=trim(path), access='stream', form='unformatted', &
status='replace', action='write', iostat=ierr)
end if
if (ierr /= 0) then
write (*, *) 'open_kipp_record: could not open ', trim(path)
return
end if
kipp_unit = u
kipp_on = .true.
! NOTE: this assumes you have already ran it once before restarting
if (.not. restart) call write_kipp_header(trim(path))
end subroutine open_kipp_record
! one-time text file naming the binary columns (help you read it)
subroutine write_kipp_header(path)
character(len=*), intent(in) :: path
integer :: uh, ierr
ierr = 0
open (newunit=uh, file=trim(path)//'.hdr', status='replace', &
action='write', iostat=ierr)
if (ierr /= 0) return
write (uh, '(a,i0)') 'ncols ', kipp_ncols
write (uh, '(a)') 'dtype float64 (little-endian), C order, row-major per cell'
write (uh, '(a)') 'columns:'
write (uh, '(a)') '1 model_number'
write (uh, '(a)') '2 star_age_yr'
write (uh, '(a)') '3 dt_s'
write (uh, '(a)') '4 zone'
write (uh, '(a)') '5 dm_g'
write (uh, '(a)') '6 m_g'
write (uh, '(a)') '7 r_cm'
write (uh, '(a)') '8 T_K'
write (uh, '(a)') '9 rho_gcc'
write (uh, '(a)') '10 mixing_type'
write (uh, '(a)') '11 eps_net_erg_g_s'
write (uh, '(a)') '12 L_erg_s'
close (uh)
end subroutine write_kipp_header
subroutine write_kipp_record(s)
type(star_info), pointer :: s
integer :: k
real(dp), allocatable :: buf(:, :)
if (.not. kipp_on) return
if (mod(s%model_number - 1, kipp_cadence) /= 0) return
allocate (buf(kipp_ncols, s%nz))
! if you want more variables, add them here!!!
do k = 1, s%nz
buf(1, k) = real(s%model_number, dp)
buf(2, k) = s%star_age
buf(3, k) = s%dt
buf(4, k) = real(k, dp)
buf(5, k) = s%dm(k)
buf(6, k) = s%m(k)
buf(7, k) = s%r(k)
buf(8, k) = s%T(k)
buf(9, k) = s%rho(k)
! NOTE: mlt_mixing_type is the END OF STEP value
! if you want the START of step, use s% mixing_type(k)
buf(10, k) = real(s%mlt_mixing_type(k), dp)
buf(11, k) = s%eps_nuc(k) - s%non_nuc_neu(k)
buf(12, k) = s%L(k)
end do
! single bulk write per step (not per cell), then flush so the file is
! readable / crash-safe mid-run
write (kipp_unit) buf
flush (kipp_unit)
deallocate (buf)
end subroutine write_kipp_record
subroutine close_kipp_record()
if (kipp_unit >= 0) then
close (kipp_unit)
kipp_unit = -1
end if
kipp_on = .false.
end subroutine close_kipp_record
An example of this implementation for star/work/src/ is held at github.com/rileythai/kippy:example/