!# This source file is part of code Pégase.3.0.1 (2019-02-21).
!# Copyright: Michel Fioc (Michel.Fioc@iap.fr), Sorbonne université, 
!# Institut d'astrophysique de Paris/CNRS, France.
!# 
!# Pégase.3.0.1 is governed by the CeCILL license under French law and abides 
!# by the rules of distribution of free software. You can use, modify and/or 
!# redistribute this software under the terms of the CeCILL license as circulated 
!# by CEA, CNRS and INRIA at "http://www.cecill.info". The text of this license
!# is also available in French and in English in directory "doc_dir/" of this
!# code.
!# 
!# As a counterpart to the access to the source code and to the rights to copy,
!# modify and redistribute it granted by the license, users are provided only
!# with a limited warranty, and the software's author, the holder of the
!# economic rights, and the successive licensors have only limited
!# liability. 
!# 
!# The fact that you are presently reading this means that you have had
!# knowledge of the CeCILL license and that you accept its terms.
!#====================================================================== 

module mod_evolution

  private
  public :: evolution

contains

!#======================================================================

  subroutine evolution(dim_convol_time, convol_time, time_step, &
       Z_SSP, dim_SSP, &
       ejec_rate_tot_SSP, ejec_rate_elem_SSP, carb_dust_prod_rate_SSP, &
       sil_dust_prod_rate_SSP, WD_mass_prod_rate_SSP, BHNS_mass_prod_rate_SSP, &
       ISM_mass, carb_init_mass, sil_init_mass, &
       ISM_elem_init_mass, carb_abund, sil_abund, &
       inert_mass, galaxy_mass, BHNS_mass, WD_mass, ISM_abund, &
       SF_live_d_mass, &
       SSP_Z_weight, &
       reserv_init_mass_scaled, &
       reserv_abund, &
       time_SSP, CCSN_rate_SSP, SNIa_rate_SSP, &
       CCSN_rate, SNIa_rate, ejec_rate_tot, SF_rate, infall_rate, &
       outflow_rate, &
       ejec_cumul_mass, SF_live_cumul_mass, &
       infall_cumul_mass, outflow_cumul_mass, &
       SF_warn_present, SF_warn_age, &
       infall_warn_present, infall_warn_age, &
       outflow_warn_present, outflow_warn_age, dim_elem, ISM_over_H &
       , Z_neb, Z_neb_weight &
       )

    use mod_types
    use mod_loc, only : i_loc
    use mod_constants, only : elem_id, &
         A_O16, A_Mg24, A_Si28, A_S32, A_Ca40, A_Fe56
    use mod_interp, only : bracket, Steffen
    use mod_scenario

    implicit none
    integer, intent(in) :: dim_convol_time
    real(CDR), dimension(:), intent(in) :: convol_time
    real(CDR), intent(in) :: time_step
    real(CDR), dimension(:), intent(in) :: Z_SSP
    integer, intent(in) :: dim_SSP
    real(CDR), dimension(:, :), intent(in) :: ejec_rate_tot_SSP
    real(CDR), dimension(:, :, 0:), intent(in) :: ejec_rate_elem_SSP
    real(CDR), dimension(:, :), intent(in) :: carb_dust_prod_rate_SSP, &
         sil_dust_prod_rate_SSP, WD_mass_prod_rate_SSP, BHNS_mass_prod_rate_SSP
    real(CDR), dimension(:), intent(inout) :: galaxy_mass, ISM_mass, &
         BHNS_mass, WD_mass, inert_mass, carb_abund, sil_abund
    real(CDR), intent(in) :: carb_init_mass, sil_init_mass
    real(CDR), dimension(0:), intent(in) :: ISM_elem_init_mass
    real(CDR), dimension(:,0:), intent(inout) :: ISM_abund
    real(DPR), dimension(:,:), pointer :: SF_live_d_mass
    real(CDR), dimension(:,:), pointer :: SSP_Z_weight
    real(CDR), dimension(:), intent(in) :: reserv_init_mass_scaled
    real(CDR), dimension(:,0:), intent(in) :: reserv_abund
    logical, intent(inout) :: SF_warn_present, outflow_warn_present
    logical, dimension(:), intent(inout) :: infall_warn_present
    real(CDR), intent(out) :: SF_warn_age, outflow_warn_age
    real(CDR), dimension(:), intent(inout) :: infall_warn_age
    real(CDR), dimension(:), intent(in) :: time_SSP
    real(CDR), dimension(:,:), intent(in) :: CCSN_rate_SSP, SNIa_rate_SSP
    real(DPR), dimension(:), intent(out) :: CCSN_rate, SNIa_rate
    real(DPR), dimension(:), intent(out) :: SF_rate
    real(DPR), dimension(:), intent(out) :: ejec_rate_tot, infall_rate, &
         outflow_rate
    real(DPR), dimension(:), intent(out) :: ejec_cumul_mass, SF_live_cumul_mass, &
         infall_cumul_mass, outflow_cumul_mass
    integer, intent(in) :: dim_elem
    real(CDR), dimension(:), intent(out) :: ISM_over_H
    real(CDR), dimension(:), intent(in) :: Z_neb
    real(CDR), dimension(:,:), intent(out) :: Z_neb_weight
!#......................................................................
    integer :: i_bracket, i_time, i_SSP, dim_time_SSP
    real(DPR), dimension(0:dim_elem) :: ejec_rate_elem, ISM_abund_prev
    real(DPR) :: carb_dust_prod_rate, sil_dust_prod_rate, BHNS_mass_prod_rate, &
         WD_mass_prod_rate, carb_abund_prev, sil_abund_prev, total_swept_mass, &
         potential_carb_mass, potential_sil_mass, ISM_over_H_prev
    integer :: i_epis
    real(CDR) :: time
    real(DPR) :: SF_d_mass, SF_inert_d_mass
    real(DPR) :: infall_d_mass
    real(DPR), dimension(size(SF_stochastic)) :: SF_stoch_time, &
         SF_stoch_modulation
    real(DPR) :: outflow_d_mass, SF_cumul_mass
    logical :: radical_outflow
    integer :: i_elem
    real(CDR) :: CCSN_max_age, time_inf, time_sup
    real(CDR) :: current_galaxy_mass, current_ISM_mass
    integer ::  i_Z, i_O, i_C, i_Fe, i_He, i_N, i_Ne, i_Mg, i_Si, i_S, i_Ca
    real(CDR), dimension(size(reserv_init_mass_scaled)) :: reserv_mass
    real(CDR), dimension(lbound(ISM_elem_init_mass, dim=1): &
         ubound(ISM_elem_init_mass, dim=1)) :: ISM_elem_mass
    real(CDR) :: carb_mass, sil_mass
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

    reserv_mass(:) = reserv_init_mass_scaled(:)
    ISM_elem_mass(:) = ISM_elem_init_mass(:)
    carb_mass = carb_init_mass
    sil_mass = sil_init_mass

    i_Z = 0
    i_O = i_loc(elem_id, "O")
    i_C = i_loc(elem_id, "C")
    i_Fe = i_loc(elem_id, "Fe")
    i_He = i_loc(elem_id, "He")
    i_N = i_loc(elem_id, "N")
    i_Ne = i_loc(elem_id, "Ne")
    i_Mg = i_loc(elem_id, "Mg")
    i_Si = i_loc(elem_id, "Si")
    i_S = i_loc(elem_id, "S")
    i_Ca = i_loc(elem_id, "Ca")

    ISM_abund_prev(0:dim_elem) = ISM_abund(1,0:dim_elem)
    carb_abund_prev = carb_abund(1)
    sil_abund_prev = sil_abund(1)
    ISM_over_H_prev = 1

!# No core-collapse supernova after `CCSN_max_age`.
    CCSN_max_age = maxval((/(maxval(time_SSP(:), &
         mask = CCSN_rate_SSP(i_SSP,:) > 0), i_SSP = 1, dim_SSP)/))

    ejec_cumul_mass(1) = 0
    SF_live_cumul_mass(1) = 0
    SF_cumul_mass = 0
    infall_cumul_mass(1) = 0
    outflow_cumul_mass(1) = 0

    current_galaxy_mass = galaxy_mass(1)
    current_ISM_mass = ISM_mass(1)

    SF_stoch_time(:) = 0

    ejec_rate_tot = 0

    ejec_rate_elem(:) = 0
    carb_dust_prod_rate = 0
    sil_dust_prod_rate = 0
    BHNS_mass_prod_rate = 0
    WD_mass_prod_rate = 0

    CCSN_rate(:) = 0
    SNIa_rate(:) = 0

    if (associated(SF_live_d_mass)) deallocate(SF_live_d_mass)
    allocate(SF_live_d_mass(dim_SSP, dim_convol_time))
    if (associated(SSP_Z_weight)) deallocate(SSP_Z_weight)
    allocate(SSP_Z_weight(dim_SSP, dim_convol_time))
    SF_live_d_mass(:,:) = 0
    SSP_Z_weight(:,:) = 0

!# `galaxy_mass(i_time)` is the value of `galaxy_mass` just before
!# `time(i_time)`. Idem for all `*_mass` quantities.

!# `infall_rate(i_time)` is the average value of `infall_rate` in
!# [`time(i_time)`, `time(i_time+1)`[. Idem for all `*_rate` quantities.

!# `SF_d_mass(i_time)` is the integral of `SF_rate(i_time)` in
!# [`time(i_time)`, `time(i_time+1)`[. Idem for all `*_d_mass` quantities.

!# Quantities modified several times during a time-step are prefixed with
!# `current`.


    dim_time_SSP = size(time_SSP)

    i_bracket = 0 !# ??? Is `i_bracket` needed?
    do i_time = 1, dim_convol_time
       time = convol_time(i_time)

!# Infall.

       call compute_infall

!# If some outflow term is "radical", remove the ISM first
!# to avoid star formation.

       radical_outflow = .false.
       do i_epis = 1, max_dim_outflow_epis
          if (outflow_end_time(i_epis) >= outflow_begin_time(i_epis) .and. &
               convol_time(i_time+1) >= outflow_begin_time(i_epis) .and. &
               convol_time(i_time) < outflow_end_time(i_epis) .and. &
               outflow_type(i_epis) == "radical") then
             radical_outflow = .true.
             exit
          endif
       enddo

!# Star formation.

       call compute_star_formation

!# End products (SN rates, stellar ejecta, remnants...).

       call convolution

!# Processes occuring in the ISM.

       call ISM_processes

!# Outflow.
!# Subroutine `compute_outflow` called after `convolution` because the
!# supernovae rates computed by the latter may be needed by the former.
       call compute_outflow

!# Computations for next time-step.

       galaxy_mass(i_time+1) = current_galaxy_mass
       ISM_mass(i_time+1) = current_ISM_mass
       BHNS_mass(i_time+1) = BHNS_mass(i_time) &
            + BHNS_mass_prod_rate * time_step
       WD_mass(i_time+1) = WD_mass(i_time) + WD_mass_prod_rate * time_step

       infall_cumul_mass(i_time+1) = infall_cumul_mass(i_time) + infall_d_mass
       SF_live_cumul_mass(i_time+1) = SF_live_cumul_mass(i_time) &
            + sum(SF_live_d_mass(:,i_time))
       ejec_cumul_mass(i_time+1) = ejec_cumul_mass(i_time) &
            + ejec_rate_tot(i_time) * time_step
       outflow_cumul_mass(i_time+1) = outflow_cumul_mass(i_time) &
            + outflow_d_mass
       inert_mass(i_time+1) = inert_mass(i_time) &
            + SF_inert_d_mass
       SF_cumul_mass = SF_live_cumul_mass(i_time+1) + inert_mass(i_time+1)

!# Following values will be modified by subroutine "convolution":
       CCSN_rate(i_time+1) = CCSN_rate(i_time)
       SNIa_rate(i_time+1) = SNIa_rate(i_time)
       ejec_rate_tot(i_time+1) = ejec_rate_tot(i_time)

    end do

  contains

!#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    subroutine compute_abundances

      implicit none

!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

      if (dust_evolution == "basic") then
         carb_mass = ISM_elem_mass(i_C) * ISM_carb_deplet !# !!! H atoms \
!# neglected.
         sil_mass = & !# !!! Not fully consistent: A_O17, etc., should be \
!# used too.
              (ISM_elem_mass(i_Mg)*(1 + O_sil_ratio*A_O16/A_Mg24) &   !# `O_sil_ratio` atom of \
!#                                                                          oxygen for each Mg atom, \
              + ISM_elem_mass(i_Si)*(1 + O_sil_ratio*A_O16/A_Si28) &  !# Si atom, \
              + ISM_elem_mass(i_S)*(1 + O_sil_ratio*A_O16/A_S32) &    !# S atom, \
              + ISM_elem_mass(i_Ca)*(1 + O_sil_ratio*A_O16/A_Ca40) &  !# Ca atom or \
              + ISM_elem_mass(i_Fe)*(1 + O_sil_ratio*A_O16/A_Fe56)) & !# Fe atom.
              * ISM_sil_deplet
      endif

      if (current_ISM_mass > 0) then
         ISM_abund(i_time,:) = ISM_elem_mass(:)/current_ISM_mass
         ISM_over_H(i_time) = &
              1/(1-ISM_abund(i_time,i_He)-ISM_abund(i_time,i_Z))
         carb_abund(i_time) = carb_mass/current_ISM_mass
         sil_abund(i_time) = sil_mass/current_ISM_mass

         ISM_abund_prev(0:dim_elem) = ISM_abund(i_time,0:dim_elem)
         carb_abund_prev = carb_abund(i_time)
         sil_abund_prev = sil_abund(i_time)
         ISM_over_H_prev = ISM_over_H(i_time)
      else !# Used in star-forming clouds even if the mass of the diffuse ISM is \
!#            null.
         ISM_abund(i_time,0:dim_elem) = ISM_abund_prev(0:dim_elem)
         carb_abund(i_time) = carb_abund_prev
         sil_abund(i_time) = sil_abund_prev
         ISM_over_H(i_time) = ISM_over_H_prev
      end if

    end subroutine compute_abundances

!#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    subroutine compute_star_formation

      use mod_random
      use mod_interp, only : compute_weights
      implicit none
!#......................................................................
      integer :: i_epis
      real(CDR), dimension(max_dim_SF_epis) :: SF_epis
      real(CDR), dimension(dim_SSP) :: SSP_epis_weight
      real(CDR) :: norm
      real(CDR) :: Z_epis
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

      SF_d_mass = 0
      SF_inert_d_mass = 0
      if (radical_outflow) then
         SF_rate(i_time) = 0
         return !# No star formation. \
!# `SF_d_mass` set to 0 above; `SF_live_d_mass` and `SSP_Z_weight` too at the \
!# beginning of subroutine `evolution`.
      endif

      do i_epis = 1, max_dim_SF_epis
         SF_epis(i_epis) = 0
         if (SF_type(i_epis) == "none") cycle

         if (SF_type(i_epis) == "instantaneous") then
!# Take the nearest time-step.
            if (nint(SF_begin_time(i_epis)/time_step)+1 == i_time) &
                 SF_epis(i_epis) = SF_inst_mass(i_epis)

         else
            if (SF_end_time(i_epis) >= SF_begin_time(i_epis) .and. &
                 convol_time(i_time+1) >= SF_begin_time(i_epis) .and. &
                 convol_time(i_time) < SF_end_time(i_epis)) then
               time_inf = max(convol_time(i_time), SF_begin_time(i_epis))
               time_sup = min(convol_time(i_time+1), SF_end_time(i_epis))
            else
               cycle
            endif

            if (SF_type(i_epis) == "constant") then
               SF_epis(i_epis) = SF_const_mass(i_epis)/ &
                    (SF_end_time(i_epis)-SF_begin_time(i_epis))* &
                    (time_sup-time_inf)

            else if (SF_type(i_epis) == "exponential") then
!# `abs()` in case `SF_expo_timescale` < 0.
               SF_epis(i_epis) = SF_expo_mass(i_epis) * &
                    abs(exp(-(time_inf-SF_begin_time(i_epis))/ &
                    SF_expo_timescale(i_epis)) &
                    - exp(-(time_sup-SF_begin_time(i_epis))/ &
                    SF_expo_timescale(i_epis)))

            else if (SF_type(i_epis) == "peaked") then
               SF_epis(i_epis) = SF_peaked_mass(i_epis)/SF_peaked_timescale(i_epis) * &
                    (exp(-(time_inf-SF_begin_time(i_epis))/ &
                    SF_peaked_timescale(i_epis)) * &
                    (time_inf-SF_begin_time(i_epis) + SF_peaked_timescale(i_epis)) &
                    - exp(-(time_sup-SF_begin_time(i_epis))/ &
                    SF_peaked_timescale(i_epis)) * &
                    (time_sup-SF_begin_time(i_epis) + SF_peaked_timescale(i_epis)))

            else if (SF_type(i_epis) == "ISM_mass") then
!# !!! The formula below overestimates the mass removed from the ISM in
!# [`time_inf`, `time_sup`[ since it assumes that `SF_epis` is constant in
!# this interval. However, because too much mass has been removed, `SF_epis`
!# should underestimate the mass removed from the ISM in the next
!# bin of time. Overall, this should compensate.
               if (current_ISM_mass > SF_ISM_threshold(i_epis)) &
                    SF_epis(i_epis) = &
                    (current_ISM_mass-SF_ISM_threshold(i_epis))**SF_ISM_power(i_epis)/ &
                    SF_ISM_timescale(i_epis)*(time_sup-time_inf)

            else if (SF_type(i_epis) == "infall") then
               SF_epis(i_epis) = SF_infall_factor(i_epis)*infall_d_mass

            else if (SF_type(i_epis) == "file") then
!# !!! Should be improved to integrate from `time_inf` to `time_sup`.
               call bracket(SF_file_dim_time(i_epis), &
                    SF_file_time(i_epis) % val, time, i_bracket)
               call Steffen(SF_file_dim_time(i_epis), &
                    SF_file_time(i_epis) % val, &
                    SF_file_rate(i_epis) % val, &
                    time, SF_epis(i_epis), i_bracket)
               SF_epis(i_epis) = SF_epis(i_epis)*(time_sup-time_inf)

            else
               write(*, "(a,i0,a)") "`SF_type(", i_epis, ")` """ // &
                    trim(SF_type(i_epis)) // """ undefined. Stopped."
               stop
            endif
         endif

         if (SF_stochastic(i_epis)) then
!# Stochastic modulation of the SFR, so that the modulated SFR
!#    1. is equal to the unmodulated SFR in the mean;
!#    2. is non negative;
!#    3. and has an r.m.s. equal to $SF_stoch_fluc$ times the unmodulated SFR.
!# A log-normal random distribution is used to this purpose.
!# The times of modulation are determined according to a Poissonian law
!# of mean $SF_stoch_timescale$.
            if (time >= SF_stoch_time(i_epis)) then
               SF_stoch_modulation(i_epis) = &
                    exp(SF_stoch_sigma(i_epis)*gaussian_random() &
                    -SF_stoch_sigma(i_epis)**2/2)
               SF_stoch_time(i_epis) = SF_stoch_time(i_epis) &
                    - SF_stoch_timescale(i_epis)*log(uniform_random(exclude_zero = .true.))
            endif
            SF_epis(i_epis) = SF_epis(i_epis)*SF_stoch_modulation(i_epis)
         endif

         SF_d_mass = SF_d_mass + SF_epis(i_epis)
         SF_inert_d_mass = SF_inert_d_mass &
              + SF_epis(i_epis)*SF_inert_frac(i_epis)

         if (SF_Z_type(i_epis) == "consistent") then
            Z_epis = ISM_abund(i_time, i_Z)
         else if (SF_Z_type(i_epis) == "constant") then
            Z_epis = SF_Z_const_val(i_epis)
         else if (SF_Z_type(i_epis) == "file") then
            call bracket(SF_Z_file_dim_time(i_epis), SF_Z_file_time(i_epis) % val, time, i_bracket)
            call Steffen(SF_Z_file_dim_time(i_epis), SF_Z_file_time(i_epis) % val, &
                 SF_Z_file_Z(i_epis) % val, time, Z_epis, i_bracket)
         else
            write(*, "(a,i0,a)") "`SF_Z_type(", i_epis, ")` """ // &
                 trim( SF_Z_type(i_epis)) // """ undefined. Stopped."
            stop
         endif

         call compute_weights(Z_SSP, Z_epis, SSP_epis_weight, &
              interp_proc="interp_log_lin")
         SF_live_d_mass(:,i_time) = SF_live_d_mass(:,i_time) + &
              SSP_epis_weight(:)*SF_epis(i_epis)*(1-SF_inert_frac(i_epis))
         SSP_Z_weight(:, i_time) = SSP_Z_weight(:, i_time) + &
              SSP_epis_weight(:)*SF_epis(i_epis)*Z_epis*(1-SF_inert_frac(i_epis))
      enddo

      where (SF_live_d_mass(:,i_time) > 0)
         SSP_Z_weight(:, i_time) = &
              SSP_Z_weight(:, i_time)/SF_live_d_mass(:,i_time) !# Put before \
!# following "if" statement because of the possible normalization of \
!# `SF_live_d_mass`.
      elsewhere
         SSP_Z_weight(:, i_time) = 0
      end where

!# ??? Should one do the following as a function of the `Z_epis`s, not of 
!# the current value of `ISM_abund(., i_Z)`? No, since emission lines
!# are determined by ISM abundances.
!#
!# ??? Should one use the current value of `ISM_abund` or the value when the
!# ionizing source was formed? Assume the first one...
      call compute_weights(Z_neb, ISM_abund(i_time, i_Z), &
           Z_neb_weight(i_time,:), interp_proc="interp_log_lin")

      if (SF_d_mass > current_ISM_mass) then
         if (.not.SF_warn_present) then
            SF_warn_age = time
            SF_warn_present = .true.
         endif
!# Warn only if `SF_d_mass` > `current_ISM_mass` + some epsilon ???
!# Scale down star formation as much as necessary: {
         norm = current_ISM_mass/SF_d_mass
         SF_d_mass = current_ISM_mass
         SF_live_d_mass(:,i_time) = SF_live_d_mass(:,i_time) * norm
         SF_inert_d_mass = SF_inert_d_mass * norm
!# }.
!# ISM evolution due to star formation: {
         current_ISM_mass = 0
         ISM_elem_mass(:) = 0
         carb_mass = 0
         sil_mass = 0
      else
         current_ISM_mass = max(0._DPR, current_ISM_mass - SF_d_mass)
         ISM_elem_mass(:) = max(0._DPR, ISM_elem_mass(:) &
              - SF_d_mass*ISM_abund(i_time,:))
!# Remark on previous line: even if the metallicity of some newly formed stars
!# is not that of the ISM, the amount of metals removed from the ISM because
!# of star formation is related to the metallicity of the ISM.
         if (dust_evolution == "Dwek") then
            carb_mass = max(0._DPR, carb_mass - SF_d_mass * carb_abund(i_time))
            sil_mass = max(0._DPR, sil_mass - &
                 SF_d_mass * sil_abund(i_time))
         endif
!# }.
      endif

      SF_rate(i_time) = SF_d_mass/time_step
      call compute_abundances

    end subroutine compute_star_formation

!#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    subroutine compute_infall

      implicit none
      integer :: i_epis, i_reserv
      real(CDR), dimension(max_dim_infall_epis) :: infall_epis
      real(CDR), dimension(max_dim_reserv) :: reserv_d_mass
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

      infall_d_mass = 0
      do i_epis = 1, max_dim_infall_epis
         infall_epis(i_epis) = 0

         if (infall_type(i_epis) == "none") cycle

         if (infall_type(i_epis) == "instantaneous") then
!# Take the nearest time-step.
            if (nint(infall_begin_time(i_epis)/time_step)+1 == i_time) &
                 infall_epis(i_epis) = infall_inst_mass(i_epis)

         else
            if (infall_end_time(i_epis) >= infall_begin_time(i_epis) .and. &
                 convol_time(i_time+1) >= infall_begin_time(i_epis) .and. &
                 convol_time(i_time) < infall_end_time(i_epis)) then
               time_inf = max(convol_time(i_time), infall_begin_time(i_epis))
               time_sup = min(convol_time(i_time+1), infall_end_time(i_epis))
            else
               cycle
            endif

            if (infall_type(i_epis) == "constant") then
               infall_epis(i_epis) = infall_const_mass(i_epis)/ &
                    (infall_end_time(i_epis)-infall_begin_time(i_epis))* &
                    (time_sup-time_inf)

            else if (infall_type(i_epis) == "exponential") then
!# `abs()` in case `infall_expo_timescale` < 0.
               infall_epis(i_epis) = infall_expo_mass(i_epis) * &
                    abs(exp(-(time_inf-infall_begin_time(i_epis))/ &
                    infall_expo_timescale(i_epis)) &
                    - exp(-(time_sup-infall_begin_time(i_epis))/ &
                    infall_expo_timescale(i_epis)))

            else if (infall_type(i_epis) == "reserv_mass") then
!# !!! The formula below overestimates the mass removed from the reservoir in
!# [`time_inf`, `time_sup`[ since it assumes that `infall_epis` is constant in
!# this interval. However, because too much mass has been removed, `infall_epis`
!# should underestimate the mass removed from the reservoir in the next
!# bin of time. Overall, this should compensate.
               infall_epis(i_epis) = reserv_mass(i_epis)**infall_reserv_power(i_epis)/ &
                    infall_reserv_timescale(i_epis)*(time_sup-time_inf)

            else if (infall_type(i_epis) == "file") then
!# !!! Should be improved to integrate from `time_inf` to `time_sup`.
               call bracket(infall_file_dim_time(i_epis), &
                    infall_file_time(i_epis) % val, time, i_bracket)
               call Steffen(infall_file_dim_time(i_epis), &
                    infall_file_time(i_epis) % val, &
                    infall_file_rate(i_epis) % val, &
                    time, infall_epis(i_epis), i_bracket)
               infall_epis(i_epis) = infall_epis(i_epis)*(time_sup-time_inf)

            else
               write(*, "(a,i0,a)") "`infall_type(", i_epis, ")` """ // &
                    trim(infall_type(i_epis)) // """ undefined. Stopped."
               stop
            endif
         endif
      enddo

      do i_reserv = 1, max_dim_reserv
         reserv_d_mass(i_reserv) = &
              sum(infall_epis(:), mask = infall_source(:) == i_reserv)
         if (reserv_d_mass(i_reserv) > reserv_mass(i_reserv)) then
            if (.not.infall_warn_present(i_reserv)) then
               infall_warn_age(i_reserv) = time
               infall_warn_present(i_reserv) = .true.
            endif
!# Warn only if `tmp` > `reserv_mass(i_reserv)` + some epsilon ???
            reserv_d_mass(i_reserv) = reserv_mass(i_reserv)
            reserv_mass(i_reserv) = 0
         else
            reserv_mass(i_reserv) = max(0._CDR, reserv_mass(i_reserv) &
                 - reserv_d_mass(i_reserv))
         endif
      enddo

      do i_reserv = 1, max_dim_reserv
         ISM_elem_mass(:) = ISM_elem_mass(:) &
              + reserv_d_mass(i_reserv)*reserv_abund(i_reserv,:)
         infall_d_mass = infall_d_mass + reserv_d_mass(i_reserv)
      enddo

      current_galaxy_mass = current_galaxy_mass + infall_d_mass
      current_ISM_mass = current_ISM_mass + infall_d_mass
      infall_rate(i_time) = infall_d_mass/time_step
      call compute_abundances

    end subroutine compute_infall

!#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    subroutine compute_outflow

      implicit none
      integer :: i_epis
      real(CDR), dimension(max_dim_outflow_epis) :: outflow_epis
      logical :: excessive_outflow
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

      outflow_d_mass = 0
      do i_epis = 1, max_dim_outflow_epis
         outflow_epis(i_epis) = 0
         if (outflow_type(i_epis) == "none" .or. outflow_type(i_epis) == "radical") &
              cycle !# If the outflow is "radical" for episode `i_epis`, the \
!# corresponding rate remains provisionally to 0 to check if the sum of other \
!# terms is larger than the maximum possible rate. The right value of \
!# `outflow_d_mass` will be computed in the block "if (radical_outflow)" below.

         if (outflow_type(i_epis) == "instantaneous") then
!# Take the nearest time-step.
            if (nint(outflow_begin_time(i_epis)/time_step)+1 == i_time) &
                 outflow_epis(i_epis) = outflow_inst_mass(i_epis)
         else
            if  (outflow_end_time(i_epis) >= outflow_begin_time(i_epis) .and. &
                 convol_time(i_time+1) >= outflow_begin_time(i_epis) .and. &
                 convol_time(i_time) < outflow_end_time(i_epis)) then
               time_inf = max(convol_time(i_time), outflow_begin_time(i_epis))
               time_sup = min(convol_time(i_time+1), outflow_end_time(i_epis))
            else
               cycle
            endif

            if (outflow_type(i_epis) == "constant") then
               outflow_epis(i_epis) = outflow_const_mass(i_epis)/ &
                    (outflow_end_time(i_epis)-outflow_begin_time(i_epis))* &
                    (time_sup-time_inf)

            else if (outflow_type(i_epis) == "SF") then
!# ??? Use rather `SF_live_d_mass` instead of `SF_d_mass`?
               outflow_epis(i_epis) = SF_d_mass*outflow_SF_factor(i_epis)/ &
                    current_ISM_mass**outflow_SF_power(i_epis)

            else if (outflow_type(i_epis) == "SN") then
               outflow_epis(i_epis) = (CCSN_rate(i_time)+SNIa_rate(i_time))* &
                    outflow_SN_mass(i_epis)*(time_sup-time_inf)/ &
                    current_ISM_mass**outflow_SN_power(i_epis)

            else if (outflow_type(i_epis) == "CCSN") then
               outflow_epis(i_epis) = CCSN_rate(i_time)*outflow_CCSN_mass(i_epis)* &
                    (time_sup-time_inf)/current_ISM_mass**outflow_CCSN_power(i_epis)

            else if (outflow_type(i_epis) == "SNIa") then
               outflow_epis(i_epis) = SNIa_rate(i_time)*outflow_SNIa_mass(i_epis)* &
                    (time_sup-time_inf)/current_ISM_mass**outflow_SNIa_power(i_epis)

            else if (outflow_type(i_epis) == "ejecta") then
!# Note: although computed from the stellar ejection rate, the outflowing matter
!# is all the interstellar medium, not only the recent stellar ejecta.
               if (current_ISM_mass < outflow_ejec_threshold(i_epis)*SF_cumul_mass) &
                    then
                  outflow_epis(i_epis) = outflow_ejec_factor(i_epis) &
                       * ejec_rate_tot(i_time) * (time_sup-time_inf) &
                       * (1-current_ISM_mass/SF_cumul_mass/ &
                       outflow_ejec_threshold(i_epis))
               endif

            else if (outflow_type(i_epis) == "file") then
!# Should be improved to integrate from `time_inf` to `time_sup`.
               call bracket(outflow_file_dim_time(i_epis), &
                    outflow_file_time(i_epis) % val, time, i_bracket)
               call Steffen(outflow_file_dim_time(i_epis), &
                    outflow_file_time(i_epis) % val, &
                    outflow_file_rate(i_epis) % val, &
                    time, outflow_epis(i_epis), i_bracket)
               outflow_epis(i_epis) = outflow_epis(i_epis)*(time_sup-time_inf)

            else
               write(*, "(a,i0,a)") "`outflow_type(", i_epis, ")` """ // &
                    trim(outflow_type(i_epis)) // """ undefined. Stopped."
               stop
            endif
         endif

         outflow_d_mass = outflow_d_mass + outflow_epis(i_epis)
      enddo

      excessive_outflow = .false.
      if (outflow_d_mass > current_ISM_mass) then
         if (.not.outflow_warn_present) then
            outflow_warn_age = time
            outflow_warn_present = .true.
         endif
!# Warn only if `outflow_d_mass` > `current_ISM_mass` + some epsilon ???
         excessive_outflow = .true.
      endif

      if (radical_outflow .or. excessive_outflow) then
         outflow_d_mass = current_ISM_mass
         current_ISM_mass = 0
         ISM_elem_mass(:) = 0
         carb_mass = 0
         sil_mass = 0
      else
         current_ISM_mass = max(0._DPR, &
              current_ISM_mass - outflow_d_mass)
         ISM_elem_mass(:) = max(0._DPR, &
              ISM_elem_mass(:) - outflow_d_mass*ISM_abund(i_time,:))
         if (dust_evolution == "Dwek") then
            carb_mass = max(0._DPR, carb_mass - &
                 outflow_d_mass*carb_abund(i_time))
            sil_mass = max(0._DPR, &
                 sil_mass - outflow_d_mass*sil_abund(i_time))
         endif
      endif

      outflow_rate(i_time) = outflow_d_mass/time_step
      current_galaxy_mass = max(0._DPR, current_galaxy_mass - outflow_d_mass)

    end subroutine compute_outflow

!#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    subroutine ISM_processes

      implicit none
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

      current_ISM_mass = current_ISM_mass &
           + ejec_rate_tot(i_time) * time_step
      ISM_elem_mass(0:dim_elem) = ISM_elem_mass(0:dim_elem) + ejec_rate_elem(0:dim_elem) * time_step
      if (dust_evolution == "Dwek") then
!# !!! The sublimation of dust grains submitted to an intense radiation
!# field is not taken into account.

!# Dust production.
         carb_mass = carb_mass + carb_dust_prod_rate * time_step
         sil_mass = sil_mass + sil_dust_prod_rate * time_step

!# Dust destruction.
         total_swept_mass = SN_swept_mass*(CCSN_rate(i_time) + &
              SNIa_rate(i_time))*time_step
         if (total_swept_mass >= current_ISM_mass) then
            carb_mass = 0
            sil_mass = 0
         else
            carb_mass = max(0._DPR, carb_mass - &
                 carb_abund(i_time)*total_swept_mass) !# ??? `carb_abund(i_time)` \
!# has not been recomputed yet.
            sil_mass = max(0._DPR, sil_mass - &
                 sil_abund(i_time)*total_swept_mass)
         endif

!# Dust accretion on grains in the ISM.
!# `potential_*_dust_mass` are the masses that would be in carbonaceous and silicate dust
!# if all the possible contributors in the ISM were in the form of grains.
         potential_carb_mass = ISM_elem_mass(i_C) !# !!! H atoms neglected.
         potential_sil_mass = & !# !!! Not fully consistent: A_O17, etc., should be \
!# used too.
              (ISM_elem_mass(i_Mg)*(1 + O_sil_ratio*A_O16/A_Mg24) &  !# `O_sil_ratio` atom of \
!#                                                                          oxygen for each Mg atom, \
              + ISM_elem_mass(i_Si)*(1 + O_sil_ratio*A_O16/A_Si28) & !# Si atom, \
              + ISM_elem_mass(i_S)*(1 + O_sil_ratio*A_O16/A_S32) &   !# S atom, \
              + ISM_elem_mass(i_Ca)*(1 + O_sil_ratio*A_O16/A_Ca40) & !# Ca atom or \
              + ISM_elem_mass(i_Fe)*(1 + O_sil_ratio*A_O16/A_Fe56))  !# Fe atom.
         if (potential_carb_mass > 0) &
              carb_mass = carb_mass &
              + max(0._DPR, (1-carb_mass/potential_carb_mass)) &
              * carb_mass/carb_accr_timescale*time_step
         if (potential_sil_mass > 0) &
              sil_mass = sil_mass &
              + max(0._DPR, (1-sil_mass/potential_sil_mass)) &
              * sil_mass/sil_accr_timescale*time_step
      endif

    end subroutine ISM_processes

!#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    subroutine convolution

      implicit none
!#......................................................................
      integer :: i_time_SSP, i_convol
!#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

!# The computation of `ejec_rate_tot(i_time)`, etc., by a direct convolution
!# is very slow. To speed it up, we assume that the quantities
!# `ejec_rate_tot_SSP(:,i_time_SSP)`, etc., may be considered as constant on
!# [`time_SSP(i_time_SSP-1)`, `time_SSP(i_time_SSP)`[. For each `i_time`, this allows to sum
!# `dim_time_SSP` terms instead of `dim_convol_time` ones. Since `dim_time_SSP` <<
!# `dim_convol_time`, this is much more rapid.
!# This requires however to use the value of `ejec_rate_tot`, etc., at
!# `i_time`-1. Numerical errors therefore increase from 1 to `dim_convol_time`.
!# {
      i_time_SSP = 1
      ejec_rate_tot(i_time) = ejec_rate_tot(i_time) + &
           dot_product(SF_live_d_mass(:,i_time), ejec_rate_tot_SSP(:,i_time_SSP))

      do i_elem = 0, dim_elem
         ejec_rate_elem(i_elem) = ejec_rate_elem(i_elem) + &
              dot_product(SF_live_d_mass(:,i_time), &
              max(0._CDR, ejec_rate_elem_SSP(:, i_time_SSP, i_elem) + &
              ISM_abund(i_time, i_elem) * ejec_rate_tot_SSP(:, i_time_SSP)))
      enddo

      carb_dust_prod_rate = carb_dust_prod_rate + &
           dot_product(SF_live_d_mass(:,i_time), carb_dust_prod_rate_SSP(:, i_time_SSP))

      sil_dust_prod_rate = sil_dust_prod_rate + &
           dot_product(SF_live_d_mass(:,i_time), sil_dust_prod_rate_SSP(:, i_time_SSP))

      BHNS_mass_prod_rate = BHNS_mass_prod_rate + &
           dot_product(SF_live_d_mass(:,i_time), BHNS_mass_prod_rate_SSP(:, i_time_SSP))

      WD_mass_prod_rate = WD_mass_prod_rate + &
           dot_product(SF_live_d_mass(:,i_time), WD_mass_prod_rate_SSP(:, i_time_SSP))

      SNIa_rate(i_time) = SNIa_rate(i_time) + &
           dot_product(SF_live_d_mass(:,i_time), SNIa_rate_SSP(:, i_time_SSP))

      do i_time_SSP = 2, dim_time_SSP
         i_convol = i_time+1-nint(time_SSP(i_time_SSP)/time_step+1) !# Dangerous???
         if (i_convol <= 0) exit
         if (all(SF_live_d_mass(:,i_convol) == 0)) cycle

         ejec_rate_tot(i_time) = ejec_rate_tot(i_time) + &
              dot_product(SF_live_d_mass(:,i_convol), &
              (ejec_rate_tot_SSP(:, i_time_SSP) - ejec_rate_tot_SSP(:, i_time_SSP-1)))

         do i_elem = 0, dim_elem
            ejec_rate_elem(i_elem) = ejec_rate_elem(i_elem) + &
                 dot_product(SF_live_d_mass(:,i_convol), &
                 (max(0._CDR, ejec_rate_elem_SSP(:, i_time_SSP, i_elem) &
                 + ISM_abund(i_convol,i_elem)*ejec_rate_tot_SSP(:, i_time_SSP)) &
                 - max(0._CDR, ejec_rate_elem_SSP(:, i_time_SSP-1, i_elem) &
                 + ISM_abund(i_convol,i_elem)*ejec_rate_tot_SSP(:, i_time_SSP-1))))
         enddo
!# Remark on previous lines: even if the initial metallicity of some stars
!# is not that of the ISM, the gross yield
!# `ejec_rate_elem_SSP+ISM_abund*ejec_rate_tot_SSP` is computed from the net
!# yield `ejec_rate_elem_SSP` using the metallicity of the ISM, not the initial
!# metallicity of the stars.

         carb_dust_prod_rate = carb_dust_prod_rate + &
              dot_product(SF_live_d_mass(:,i_convol), &
              (carb_dust_prod_rate_SSP(:, i_time_SSP) - &
              carb_dust_prod_rate_SSP(:, i_time_SSP-1)))

         sil_dust_prod_rate = sil_dust_prod_rate + &
              dot_product(SF_live_d_mass(:,i_convol), &
              (sil_dust_prod_rate_SSP(:, i_time_SSP) - &
              sil_dust_prod_rate_SSP(:, i_time_SSP-1)))

         BHNS_mass_prod_rate = BHNS_mass_prod_rate + &
              dot_product(SF_live_d_mass(:,i_convol), &
              (BHNS_mass_prod_rate_SSP(:, i_time_SSP) - &
              BHNS_mass_prod_rate_SSP(:, i_time_SSP-1)))

         WD_mass_prod_rate = WD_mass_prod_rate + &
              dot_product(SF_live_d_mass(:,i_convol), &
              (WD_mass_prod_rate_SSP(:, i_time_SSP) - &
              WD_mass_prod_rate_SSP(:, i_time_SSP-1)))

         SNIa_rate(i_time) = SNIa_rate(i_time) + &
              dot_product(SF_live_d_mass(:,i_convol), &
              (SNIa_rate_SSP(:, i_time_SSP) - &
              SNIa_rate_SSP(:, i_time_SSP-1)))
      enddo
!# }.
      ejec_rate_tot(i_time) = max(0._DPR, ejec_rate_tot(i_time))
      ejec_rate_elem(:) = max(0._DPR, ejec_rate_elem(:))
      carb_dust_prod_rate = max(0._DPR, carb_dust_prod_rate)
      sil_dust_prod_rate = max(0._DPR, sil_dust_prod_rate)
      BHNS_mass_prod_rate = max(0._DPR, BHNS_mass_prod_rate)
      WD_mass_prod_rate = max(0._DPR, WD_mass_prod_rate)
      SNIa_rate(i_time) = max(0._DPR, SNIa_rate(i_time))

!# Since core-collapse supernovae occur only for a few tens of millions of years,
!# there is no need to use the trick mentionned above. Since `CCSN_rate(i_time)`
!# does not depend on `CCSN_rate(i_time-1)`, it should be more accurate.
      CCSN_rate(i_time) = 0
      do i_time_SSP = 1, dim_time_SSP-1
         if (time_SSP(i_time_SSP) > time) exit
         do i_convol = nint(time_SSP(i_time_SSP)/time_step+1), &
              min(i_time, nint(time_SSP(i_time_SSP+1)/time_step))
            if (all(SF_live_d_mass(:,i_time+1-i_convol) == 0)) cycle
            if (time_SSP(i_time_SSP) > CCSN_max_age) exit
            CCSN_rate(i_time) = CCSN_rate(i_time) &
                 + dot_product(SF_live_d_mass(:,i_time+1-i_convol), CCSN_rate_SSP(:, i_time_SSP))
         enddo
      enddo

    end subroutine convolution

  end subroutine evolution

end module mod_evolution
