From fb21ff7e17451588f65bbdcfbfbbba96591b1690 Mon Sep 17 00:00:00 2001 From: David A Minton Date: Wed, 4 Aug 2021 20:36:45 -0400 Subject: [PATCH] Added energy and momentum report back in --- src/io/io.f90 | 86 ++++++++++++++++++ src/modules/swiftest_classes.f90 | 62 ++++++++++--- src/obl/obl.f90 | 40 +++++++++ src/util/util_get_energy_momentum.f90 | 121 ++++++++++++++++++++++++++ 4 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 src/util/util_get_energy_momentum.f90 diff --git a/src/io/io.f90 b/src/io/io.f90 index 145752836..e159d019d 100644 --- a/src/io/io.f90 +++ b/src/io/io.f90 @@ -2,6 +2,92 @@ use swiftest contains + module subroutine io_conservation_report(self, param, lterminal) + !! author: The Purdue Swiftest Team - David A. Minton, Carlisle A. Wishard, Jennifer L.L. Pouplin, and Jacob R. Elliott + !! + !! Reports the current state of energy, mass, and angular momentum conservation in a run + implicit none + ! Arguments + class(swiftest_nbody_system), intent(inout) :: self !! Swiftest nbody system object + class(swiftest_parameters), intent(inout) :: param !! Input colleciton of user-defined parameters + logical, intent(in) :: lterminal !! Indicates whether to output information to the terminal screen + ! Internals + real(DP), dimension(NDIM) :: Ltot_now, Lorbit_now, Lspin_now + real(DP), dimension(NDIM), save :: Ltot_last, Lorbit_last, Lspin_last + real(DP), save :: ke_orbit_last, ke_spin_last, pe_last, Eorbit_last + real(DP) :: ke_orbit_now, ke_spin_now, pe_now, Eorbit_now + real(DP) :: Eorbit_error, Etotal_error, Ecoll_error + real(DP) :: Mtot_now, Merror + real(DP) :: Lmag_now, Lerror + character(len=*), parameter :: EGYFMT = '(ES23.16,10(",",ES23.16,:))' ! Format code for all simulation output + character(len=*), parameter :: EGYHEADER = '("t,Eorbit,Ecollisions,Lx,Ly,Lz,Mtot")' + integer(I4B), parameter :: EGYIU = 72 + character(len=*), parameter :: EGYTERMFMT = '(" DL/L0 = ", ES12.5 & + "; DEcollisions/|E0| = ", ES12.5, & + "; D(Eorbit+Ecollisions)/|E0| = ", ES12.5, & + "; DM/M0 = ", ES12.5)' + + associate(system => self, pl => self%pl, cb => self%cb, npl => self%pl%nbody, Ecollisions => self%Ecollisions, Lescape => self%Lescape, Mescape => self%Mescape, & + Euntracked => self%Euntracked, Eorbit_orig => param%Eorbit_orig, Mtot_orig => param%Mtot_orig, & + Ltot_orig => param%Ltot_orig(:), Lmag_orig => param%Lmag_orig, Lorbit_orig => param%Lorbit_orig(:), Lspin_orig => param%Lspin_orig(:), & + lfirst => param%lfirstenergy) + if (lfirst) then + if (param%out_stat == "OLD") then + open(unit = EGYIU, file = ENERGY_FILE, form = "formatted", status = "old", action = "write", position = "append") + else + open(unit = EGYIU, file = ENERGY_FILE, form = "formatted", status = "replace", action = "write") + write(EGYIU,EGYHEADER) + end if + end if + call system%get_energy_and_momentum(param, ke_orbit_now, ke_spin_now, pe_now, Lorbit_now, Lspin_now) + Eorbit_now = ke_orbit_now + ke_spin_now + pe_now + Ltot_now(:) = Lorbit_now(:) + Lspin_now(:) + Lescape(:) + Mtot_now = cb%mass + sum(pl%mass(1:npl)) + system%Mescape + if (lfirst) then + Eorbit_orig = Eorbit_now + Mtot_orig = Mtot_now + Lorbit_orig(:) = Lorbit_now(:) + Lspin_orig(:) = Lspin_now(:) + Ltot_orig(:) = Ltot_now(:) + Lmag_orig = norm2(Ltot_orig(:)) + lfirst = .false. + end if + + write(EGYIU,EGYFMT) param%t, Eorbit_now, Ecollisions, Ltot_now, Mtot_now + flush(EGYIU) + if (.not.lfirst .and. lterminal) then + Lmag_now = norm2(Ltot_now) + Lerror = norm2(Ltot_now - Ltot_orig) / Lmag_orig + Eorbit_error = (Eorbit_now - Eorbit_orig) / abs(Eorbit_orig) + Ecoll_error = Ecollisions / abs(Eorbit_orig) + Etotal_error = (Eorbit_now - Ecollisions - Eorbit_orig - Euntracked) / abs(Eorbit_orig) + Merror = (Mtot_now - Mtot_orig) / Mtot_orig + write(*, egytermfmt) Lerror, Ecoll_error, Etotal_error, Merror + if (Ecoll_error > 0.0_DP) then + write(*,*) 'Something has gone wrong! Collisional energy should not be positive!' + write(*,*) 'dke_orbit: ',(ke_orbit_now - ke_orbit_last) / abs(Eorbit_orig) + write(*,*) 'dke_spin : ',(ke_spin_now - ke_spin_last) / abs(Eorbit_orig) + write(*,*) 'dpe : ',(pe_now - pe_last) / abs(Eorbit_orig) + write(*,*) + end if + if (Lerror > 1e-6) then + write(*,*) 'Something has gone wrong! Angular momentum is too high!' + write(*,*) 'Lerror: ', Lerror + end if + end if + ke_orbit_last = ke_orbit_now + ke_spin_last = ke_spin_now + pe_last = pe_now + Eorbit_last = Eorbit_now + Lorbit_last(:) = Lorbit_now(:) + Lspin_last(:) = Lspin_now(:) + Ltot_last(:) = Ltot_now(:) + end associate + return + + end subroutine io_conservation_report + + module subroutine io_dump_param(self, param_file_name) !! author: David A. Minton !! diff --git a/src/modules/swiftest_classes.f90 b/src/modules/swiftest_classes.f90 index 74add9081..dcff0f6d8 100644 --- a/src/modules/swiftest_classes.f90 +++ b/src/modules/swiftest_classes.f90 @@ -45,8 +45,9 @@ module swiftest_classes real(QP) :: DU2M = -1.0_QP !! Converts distance unit to centimeters real(DP) :: GU = -1.0_DP !! Universal gravitational constant in the system units real(DP) :: inv_c2 = -1.0_DP !! Inverse speed of light squared in the system units + character(STRMAX) :: ennergy_out = "" !! Name of output energy and momentum report file - !Logical flags to turn on or off various features of the code + ! Logical flags to turn on or off various features of the code logical :: lrhill_present = .false. !! Hill radii are given as an input rather than calculated by the code (can be used to inflate close encounter regions manually) logical :: lextra_force = .false. !! User defined force function turned on logical :: lbig_discard = .false. !! Save big bodies on every discard @@ -56,6 +57,16 @@ module swiftest_classes logical :: lrotation = .false. !! Include rotation states of big bodies logical :: ltides = .false. !! Include tidal dissipation + ! Initial values to pass to the energy report subroutine (usually only used in the case of a restart, otherwise these will be updated with initial conditions values) + real(DP) :: Eorbit_orig = 0.0_DP !! Initial orbital energy + real(DP) :: Mtot_orig = 0.0_DP !! Initial system mass + real(DP) :: Lmag_orig = 0.0_DP !! Initial total angular momentum magnitude + real(DP), dimension(NDIM) :: Ltot_orig = 0.0_DP !! Initial total angular momentum vector + real(DP), dimension(NDIM) :: Lorbit_orig = 0.0_DP !! Initial orbital angular momentum + real(DP), dimension(NDIM) :: Lspin_orig = 0.0_DP !! Initial spin angular momentum vector + logical :: lfirstenergy = .true. !! This is the first time computing energe + logical :: lfirstkick = .true. !! Initiate the first kick in a symplectic step + ! Future features not implemented or in development logical :: lgr = .false. !! Turn on GR logical :: lyarkovsky = .false. !! Turn on Yarkovsky effect @@ -259,7 +270,7 @@ module swiftest_classes class(swiftest_pl), allocatable :: pl !! Massive body data structure class(swiftest_tp), allocatable :: tp !! Test particle data structure class(swiftest_tp), allocatable :: tp_discards !! Discarded test particle data structure - real(DP) :: Gmtot = 0.0_DP !! Total system mass - used for barycentric coordinate conversion + real(DP) :: Gmtot = 0.0_DP !! Total system mass - used for barycentric coordinate conversion real(DP) :: ke = 0.0_DP !! System kinetic energy real(DP) :: pe = 0.0_DP !! System potential energy real(DP) :: te = 0.0_DP !! System total energy @@ -276,14 +287,16 @@ module swiftest_classes procedure(abstract_step_system), deferred :: step ! Concrete classes that are common to the basic integrator (only test particles considered for discard) - procedure :: discard => discard_system !! Perform a discard step on the system - procedure :: dump => io_dump_system !! Dump the state of the system to a file - procedure :: read_frame => io_read_frame_system !! Read in a frame of input data from file - procedure :: write_discard => io_write_discard !! Write out information about discarded test particles - procedure :: write_frame => io_write_frame_system !! Append a frame of output data to file - procedure :: initialize => setup_initialize_system !! Initialize the system from input files - procedure :: step_spin => tides_step_spin_system !! Steps the spins of the massive & central bodies due to tides. - procedure :: set_msys => util_set_msys !! Sets the value of msys from the masses of system bodies. + procedure :: discard => discard_system !! Perform a discard step on the system + procedure :: conservation_report => io_conservation_report !! Compute energy and momentum and print out the change with time + procedure :: dump => io_dump_system !! Dump the state of the system to a file + procedure :: read_frame => io_read_frame_system !! Read in a frame of input data from file + procedure :: write_discard => io_write_discard !! Write out information about discarded test particles + procedure :: write_frame => io_write_frame_system !! Append a frame of output data to file + procedure :: initialize => setup_initialize_system !! Initialize the system from input files + procedure :: step_spin => tides_step_spin_system !! Steps the spins of the massive & central bodies due to tides. + procedure :: set_msys => util_set_msys !! Sets the value of msys from the masses of system bodies. + procedure :: get_energy_and_momentum => util_get_energy_momentum_system !! Calculates the total system energy and momentum end type swiftest_nbody_system type :: swiftest_encounter @@ -487,6 +500,13 @@ module pure subroutine gr_vh2pv_body(self, param) class(swiftest_parameters), intent(in) :: param !! Current run configuration parameters end subroutine gr_vh2pv_body + module subroutine io_conservation_report(self, param, lterminal) + implicit none + class(swiftest_nbody_system), intent(inout) :: self !! Swiftest nbody system object + class(swiftest_parameters), intent(inout) :: param !! Input colleciton of user-defined parameters + logical, intent(in) :: lterminal !! Indicates whether to output information to the terminal screen + end subroutine io_conservation_report + module subroutine io_dump_param(self, param_file_name) implicit none class(swiftest_parameters),intent(in) :: self !! Output collection of parameters @@ -662,6 +682,17 @@ module subroutine obl_acc_tp(self, system) class(swiftest_nbody_system), intent(inout) :: system !! Swiftest nbody system object end subroutine obl_acc_tp + module subroutine obl_pot(npl, Mcb, Mpl, j2rp2, j4rp4, xh, irh, oblpot) + implicit none + integer(I4B), intent(in) :: npl + real(DP), intent(in) :: Mcb + real(DP), dimension(:), intent(in) :: Mpl + real(DP), intent(in) :: j2rp2, j4rp4 + real(DP), dimension(:), intent(in) :: irh + real(DP), dimension(:, :), intent(in) :: xh + real(DP), intent(out) :: oblpot + end subroutine obl_pot + module subroutine orbel_el2xv_vec(self, cb) implicit none class(swiftest_body), intent(inout) :: self !! Swiftest body object @@ -983,6 +1014,17 @@ module subroutine util_resize_tp(self, nnew) integer(I4B), intent(in) :: nnew !! New size neded end subroutine util_resize_tp + module subroutine util_get_energy_momentum_system(self, param, ke_orbit, ke_spin, pe, Lorbit, Lspin) + implicit none + class(swiftest_nbody_system), intent(inout) :: self !! Swiftest nbody system object + class(swiftest_parameters), intent(in) :: param !! Current run configuration parameters + real(DP), intent(out) :: ke_orbit !! Orbital kinetic energy + real(DP), intent(out) :: ke_spin !! Spin kinetic energy + real(DP), intent(out) :: pe !! Potential energy + real(DP), dimension(:), intent(out) :: Lorbit !! Orbital angular momentum + real(DP), dimension(:), intent(out) :: Lspin !! Spin angular momentum + end subroutine util_get_energy_momentum_system + module subroutine util_set_beg_end_pl(self, xbeg, xend, vbeg) implicit none class(swiftest_pl), intent(inout) :: self !! Swiftest massive body object diff --git a/src/obl/obl.f90 b/src/obl/obl.f90 index 91b20b62b..035a54b18 100644 --- a/src/obl/obl.f90 +++ b/src/obl/obl.f90 @@ -106,5 +106,45 @@ module subroutine obl_acc_tp(self, system) end subroutine obl_acc_tp + module subroutine obl_pot(npl, Mcb, Mpl, j2rp2, j4rp4, xh, irh, oblpot) + !! author: David A. Minton + !! + !! Compute the contribution to the total gravitational potential due solely to the oblateness of the central body + !! Returned value does not include monopole term or terms higher than J4 + !! + !! Reference: MacMillan, W. D. 1958. The Theory of the Potential, (Dover Publications), 363. + !! + !! Adapted from David E. Kaufmann's Swifter routine: obl_pot.f90 + !! Adapted from Hal Levison's Swift routine obl_pot.f + implicit none + ! Arguments + integer(I4B), intent(in) :: npl + real(DP), intent(in) :: Mcb + real(DP), dimension(:), intent(in) :: Mpl + real(DP), intent(in) :: j2rp2, j4rp4 + real(DP), dimension(:), intent(in) :: irh + real(DP), dimension(:, :), intent(in) :: xh + real(DP), intent(out) :: oblpot + + ! Internals + integer(I4B) :: i + real(DP) :: rinv2, t0, t1, t2, t3, p2, p4, mu + + oblpot = 0.0_DP + mu = Mcb + do i = 1, npl + rinv2 = irh(i)**2 + t0 = mu * Mpl(i) * rinv2 * irh(i) + t1 = j2rp2 + t2 = xh(3, i) * xh(3, i) * rinv2 + t3 = j4rp4 * rinv2 + p2 = 0.5_DP * (3 * t2 - 1.0_DP) + p4 = 0.125_DP * ((35 * t2 - 30.0_DP) * t2 + 3.0_DP) + oblpot = oblpot + t0 * (t1 * p2 + t3 * p4) + end do + + return + end subroutine obl_pot + end submodule s_obl diff --git a/src/util/util_get_energy_momentum.f90 b/src/util/util_get_energy_momentum.f90 new file mode 100644 index 000000000..69936e1b2 --- /dev/null +++ b/src/util/util_get_energy_momentum.f90 @@ -0,0 +1,121 @@ +submodule (swiftest_classes) s_util_get_energy_momentum + use swiftest +contains + module subroutine util_get_energy_momentum_system(self, param, ke_orbit, ke_spin, pe, Lorbit, Lspin) + !! author: David A. Minton + !! + !! Compute total system angular momentum vector and kinetic, potential and total system energy + !! + !! Adapted from David E. Kaufmann Swifter routine symba_energy_eucl.f90 + !! + !! Adapted from Martin Duncan's Swift routine anal_energy.f + implicit none + class(swiftest_nbody_system), intent(inout) :: self !! Swiftest nbody system object + class(swiftest_parameters), intent(in) :: param !! Current run configuration parameters + real(DP), intent(out) :: ke_orbit !! Orbital kinetic energy + real(DP), intent(out) :: ke_spin !! Spin kinetic energy + real(DP), intent(out) :: pe !! Potential energy + real(DP), dimension(:), intent(out) :: Lorbit !! Orbital angular momentum + real(DP), dimension(:), intent(out) :: Lspin !! Spin angular momentum + ! Internals + integer(I4B) :: i, j + integer(I8B) :: k + real(DP) :: rmag, v2, rot2, oblpot, hx, hy, hz, hsx, hsy, hsz + real(DP), dimension(self%pl%nbody) :: irh, kepl, kespinpl, pecb + real(DP), dimension(self%pl%nbody) :: Lplorbitx, Lplorbity, Lplorbitz + real(DP), dimension(self%pl%nbody) :: Lplspinx, Lplspiny, Lplspinz + real(DP), dimension(self%pl%nplpl) :: pepl + logical, dimension(self%pl%nplpl) :: lstatpl + logical, dimension(self%pl%nbody) :: lstatus + + Lorbit(:) = 0.0_DP + Lspin(:) = 0.0_DP + ke_orbit = 0.0_DP + ke_spin = 0.0_DP + associate(system => self, pl => self%pl, npl => self%pl%nbody, cb => self%cb) + kepl(:) = 0.0_DP + Lplorbitx(:) = 0.0_DP + Lplorbity(:) = 0.0_DP + Lplorbitz(:) = 0.0_DP + Lplspinx(:) = 0.0_DP + Lplspiny(:) = 0.0_DP + Lplspinz(:) = 0.0_DP + lstatus(1:npl) = pl%status(1:npl) /= INACTIVE + !!$omp simd private(v2, rot2, hx, hy, hz) + do i = 1, npl + v2 = dot_product(pl%vb(:,i), pl%vb(:,i)) + hx = pl%xb(2,i) * pl%vb(3,i) - pl%xb(3,i) * pl%vb(2,i) + hy = pl%xb(3,i) * pl%vb(1,i) - pl%xb(1,i) * pl%vb(3,i) + hz = pl%xb(1,i) * pl%vb(2,i) - pl%xb(2,i) * pl%vb(1,i) + + ! Angular momentum from orbit + Lplorbitx(i) = pl%mass(i) * hx + Lplorbity(i) = pl%mass(i) * hy + Lplorbitz(i) = pl%mass(i) * hz + + ! Kinetic energy from orbit and spin + kepl(i) = pl%mass(i) * v2 + end do + if (param%lrotation) then + do i = 1, npl + rot2 = dot_product(pl%rot(:,i), pl%rot(:,i)) + ! For simplicity, we always assume that the rotation pole is the 3rd principal axis + hsx = pl%Ip(3,i) * pl%radius(i)**2 * pl%rot(1,i) + hsy = pl%Ip(3,i) * pl%radius(i)**2 * pl%rot(2,i) + hsz = pl%Ip(3,i) * pl%radius(i)**2 * pl%rot(3,i) + + ! Angular momentum from spin + Lplspinx(i) = pl%mass(i) * hsx + Lplspiny(i) = pl%mass(i) * hsy + Lplspinz(i) = pl%mass(i) * hsz + kespinpl(i) = pl%mass(i) * pl%Ip(3, i) * pl%radius(i)**2 * rot2 + end do + else + kespinpl(:) = 0.0_DP + end if + + ! Do the central body potential energy component first + !$omp simd + do i = 1, npl + associate(px => pl%xh(1,i), py => pl%xh(2,i), pz => pl%xh(3,i)) + pecb(i) = -cb%mass * pl%mass(i) / sqrt(px**2 + py**2 + pz**2) + end associate + end do + + ! Do the potential energy between pairs of massive bodies + do k = 1, pl%nplpl + associate(ik => pl%k_plpl(1, k), jk => pl%k_plpl(2, k)) + pepl(k) = -pl%mass(ik) * pl%mass(jk) / norm2(pl%xb(:, jk) - pl%xb(:, ik)) + lstatpl(k) = (lstatus(ik) .and. lstatus(jk)) + end associate + end do + + ke_orbit = 0.5_DP * sum(kepl(1:npl), lstatus(:)) + if (param%lrotation) ke_spin = 0.5_DP * sum(kespinpl(1:npl), lstatus(:)) + + pe = sum(pepl(:), lstatpl(:)) + sum(pecb(2:npl), lstatus(2:npl)) + + ! Potential energy from the oblateness term + if (param%loblatecb) then + !$omp simd + do i = 1, npl + irh(i) = 1.0_DP / norm2(pl%xh(:,i)) + end do + call obl_pot(npl, cb%mass, pl%mass, cb%j2rp2, cb%j4rp4, pl%xh, irh, oblpot) + pe = pe + oblpot + end if + + Lorbit(1) = sum(Lplorbitx(1:npl), lstatus(1:npl)) + Lorbit(2) = sum(Lplorbity(1:npl), lstatus(1:npl)) + Lorbit(3) = sum(Lplorbitz(1:npl), lstatus(1:npl)) + + Lspin(1) = sum(Lplspinx(1:npl), lstatus(1:npl)) + Lspin(2) = sum(Lplspiny(1:npl), lstatus(1:npl)) + Lspin(3) = sum(Lplspinz(1:npl), lstatus(1:npl)) + + end associate + + return + end subroutine util_get_energy_momentum_system + +end submodule s_util_get_energy_momentum