def mass_loss_rate_forward_LO14(t_, epsilon, K_on, beta_on,
                                planet_object, f_env, track_dict):
    """ Calculate the XUV-induced mass-loss rate at any given time step
    (of the integration) for a Lopez & Fortney (2014) planet with rocky core
    and H/He envelope (see Planet_models_LoFo14 for details).
    (see e.g. Lammer et al. 2003, Owen & Wu 2013; Lopez & Fortney 2013 for
    details on XUV-induced mass loss/ photoevaporation)

    Parameters:
    -----------
    t_ (float): time (in Myr)
    epsilon (float): evaporation efficiency
    K_on (float): set use of K parameter on or off ("on" or "off)
    beta_on (float): set use of beta parameter on or off ("on" or "off)
    planet_object (class obj): object of planet class which contains
                               planetray & stellar parameters (here we read out
                               the core mass, and together with the envelope
                               mass fraction at time t_ we can calcualte the
                               current mass and radius of the planet)
    f_env (float): envelope mass fraction at time t_
    track_dict (dict): dictionary with evolutionary track parameters

    Returns:
    --------
    mass-loss rate in cgs units (g/s) -> NOTE: mass-loss rate is negative!
    """

    # calculate X-ray luminosity and planet flux at time t_
    Lx = lx_evo(t=t_, track_dict=track_dict)
    Fxuv = flux_at_planet(l_xuv_all(Lx), planet_object.distance)

    # calculate current planet density
    R_p = plmoLoFo14.calculate_planet_radius(planet_object.core_mass,
                                             f_env, t_,
                                             flux_at_planet_earth(
                                                    planet_object.Lbol,
                                                    planet_object.distance),
                                             planet_object.metallicity)
    M_p = plmoLoFo14.calculate_planet_mass(planet_object.core_mass, f_env)
    rho_p = rho = plmoLoFo14.density_planet(M_p, R_p)  # mean density

    # specify beta and K
    if beta_on == "yes":
        beta = bk.beta_fct(M_p, Fxuv, R_p)
    elif beta_on == "no":
        beta = 1.
    if K_on == "yes":
        K = bk.K_fct(planet_object.distance, M_p, 
        			 planet_object.mass_star, R_p)
    elif K_on == "no":
        K = 1.

    num = 3. * beta**2. * epsilon * Fxuv
    denom = 4. * const.G.cgs * K * rho_p
    M_dot = num / denom

    # NOTE: I define my mass loss rate to be negative, this way the
    # planet loses mass as I integrate forward in time.
    return -(M_dot.decompose(bases=u.cgs.bases)).value  # in cgs units [g/s]
def mass_loss_rate_forward_Ot20(t_, epsilon, K_on, beta_on,
                                planet_object, radius_at_t, track_dict):
    """ Calculate the XUV-induced mass-loss rate at any given time step
    (of the integration) for a planet which follows the "mature" mass-radius
    relation as given in Otegi et al. (2020) (see Planet_models_Ot20 for
    details).
    (see e.g. Lammer et al. 2003, Owen & Wu 2013; Lopez & Fortney 2013 for
    details on XUV-induced mass loss/ photoevaporation)

    Parameters:
    -----------
    t_ (float): time (in Myr)
    epsilon (float): evaporation efficiency
    K_on (float): set use of K parameter on or off ("on" or "off)
    beta_on (float): set use of beta parameter on or off ("on" or "off)
    planet_object (class obj): object of planet class which contains
                               planetray & stellar parameters
    radius_at_t (float): current planetary radius at time t_; used to estimate
                         the new planet mass at time t_
    track_dict (dict): dictionary with evolutionary track parameters

    Returns:
    --------
    mass-loss rate in cgs units (g/s) -> NOTE: mass-loss rate is negative!
    """

    # calculate X-ray luminosity and planet flux at time t_
    Lx = lx_evo(t=t_, track_dict=track_dict)
    Fxuv = flux_at_planet(l_xuv_all(Lx), planet_object.distance)

    # estimate planetary mass using the Otegi 2020 mass-radius relation
    # and then calculate current mean density
    M_p = plmoOt20.calculate_mass_planet_Ot20(radius_at_t)
    rho_p = rho = plmoOt20.density_planet(M_p, radius_at_t)  # mean density

    # specify beta and K
    if beta_on == "yes":
        beta = bk.beta_fct(M_p, Fxuv, radius_at_t)
    elif beta_on == "no":
        beta = 1.
    if K_on == "yes":
        K = bk.K_fct(planet_object.distance, M_p,
                     planet_object.mass_star, radius_at_t)
    elif K_on == "no":
        K = 1.

    num = 3. * beta**2. * epsilon * Fxuv
    denom = 4. * const.G.cgs * K * rho_p
    M_dot = num / denom

    # NOTE: I define my mass loss rate to be negative, this way the
    # planet loses mass as I integrate forward in time.
    return -(M_dot.decompose(bases=u.cgs.bases)).value  # in cgs units [g/s]
Example #3
0
def mass_planet_RK4_forward_Ot20(epsilon, K_on, beta_on, planet_object,
                                 initial_step_size, t_final, track_dict):
    """USED: 4th order Runge-Kutta as numerical integration  method 
    Integrate from the current time (t_start (where planet has R0 and M0)
    into the future taking into account photoevaporative mass loss. 
    
    Parameters:
    -----------
    epsilon (float): evaporation efficiency
    K_on (str): set use of K parameter on or off ("on" or "off)
    beta_on (str): set use of beta parameter on or off ("on" or "off)
    planet_object: object of planet class which contains also stellar 
                   parameters and info about stellar evo track
    step_size (float): initial step_size, variable
    t_final (float): final time of simulation
    track_dict (dict): dictionary with Lx evolutionary track parameters
    
    [NOTE: the implementation of a variable step size is somewhat preliminary.
    The step size is adjusted (made smaller or bigger depending how fast or
    slow the mass/radius changes) until the final time step greater than
    t_final. This means that if the step size in the end is e.g. 10 Myr, and
    the integration is at 4999 Myr, then last time entry will be 4999+10 ->
    5009 Myr.]

    Returns:
    --------
    t_arr (array): time array to trace mass and radius evolution
    M_arr (array): mass array with mass evolution over time (mass decrease)
    R_arr (array): radius array with radius evolution over time (from
                   thermal contraction and photoevaporative mass-loss)
    Lx_arr (array): array to trace the X-ray luminosity (mainly for
                    consistency checks)
    """

    M_EARTH = const.M_earth.cgs.value
    Myr_to_sec = 1e6 * 365 * 86400

    # initialize the starting values for Lxuv(t_start), mass, density, beta, K
    Lx0 = lx_evo(t=track_dict["t_start"], track_dict=track_dict)
    Lxuv0 = l_xuv_all(Lx0)
    Fxuv0 = flux_at_planet(Lxuv0, planet_object.distance)

    # "make" initial planet at t_start
    R0 = R = planet_object.radius
    M0 = M = planet_object.mass
    rho0 = rho = plmoOt20.density_planet(M0, R0)  # initial approx. density

    # specify beta0 and K0
    if beta_on == "yes":
        beta = beta0 = bk.beta_fct(M0, Fxuv0, R0)
    elif beta_on == "no":
        beta = beta0 = 1.
    if K_on == "yes":
        K = K0 = bk.K_fct(planet_object.distance, M0, planet_object.mass_star,
                          R0)
    elif K_on == "no":
        K = K0 = 1.

    M_arr = []
    M_arr.append(M0)
    R_arr = []
    R_arr.append(R0)
    t_arr = []
    t0 = t = track_dict["t_start"]
    t_arr.append(t0)
    Lx_arr = []
    Lx_arr.append(Lx0)

    # CRITERION when to stop the mass-loss
    # stop when this hardcoded radius is reached!
    # (this is the minimum radius for which the volatile regime is valid)
    R_core = 2.15
    M_core = plmoOt20.calculate_mass_planet_Ot20(R_core)

    dt = initial_step_size
    # NOTE: minimum and maximum step size are HARDCODED for now (see further
    # down in code for more details)
    min_step_size, max_step_size = 1e-2, 10.

    i = 1  # counter to track how many traced RK iterations have been performed
    j = 1  # counter to track how many RK iterations have been attempted.
    envelope_left = True  # variable to flag a planet if envelope is gone
    close_to_evaporation = False  # variable to flag if planet is close to
    # complete atmospheric removal

    # make list with all step sizes, even those which resulted in too drastic
    # radius changes -> then I can check if the code gets stuck in an infinite
    # loop between make_bigger, make_smaller, make_bigger, etc..
    step_size_list = []

    while t <= t_final:
        #print(i, j, " t= ", t, dt)

        # This step (Lx(t) calculation) is just for me to trace Lx and check
        # if it is correct. It is NOT required since the Lx(t) calculation is
        # embedded in the mass_loss_rate_fancy function)
        Lx_i = lx_evo(t=t, track_dict=track_dict)

        # IMPORTANT points on the time step:
        # When the initial time step is too large OR the planet mass becomes
        # very close to the core mass (after several time steps), it can happen
        # that one of the RK substeps leads to such a large mass lost that the
        # new planet mass is smaller than the core mass.
        # Distinguish between two cases:
        # 1) initial time step is too large such that M_lost = nan after the
        # first iteration (i.e. Rk substep mass < core mass)
        # -> immediately switch to lowest possible step size and let code run
        # from there (i.e. code will make step size bigger again if necessary)
        # 2) at the end of planet evolution when the planet mass gets very
        # close to the core mass, at some point the mass lost is larger than
        # the renmaining atmosphere mass (either at the end of a coplete RK
        # iteration, or this already happends in one of the RK substeps, in
        # which case the mass lost after the complete RK step evaluates to nan
        # and no new planet radius can be calculated). In both cases the planet
        # is assumed to be fully evaporated at t_i + dt.

        # add the current step size
        step_size_list.append(dt)
        # take the last 20 entries in step_size_array and check for
        # constant back-and-forth between two step sizes
        # e.g. [0.1, 1.0, 0.1, 1.0, ...]
        # check if difference is the same, but values in array are not all
        # the same; also need to check if every 2nd element is the same,
        # same goes for 2(n+1)th
        step_size_array = np.array(step_size_list)
        step_size_difference = abs(np.diff(step_size_array[-20:]))
        if (len(step_size_array) >= 20):  # check only after a 20 iterations
            if (np.all(step_size_difference == step_size_difference[0])
                    and ~np.all(step_size_array == step_size_array[0])
                    and np.all(step_size_array[::2] == step_size_array[0])
                    and np.all(step_size_array[1::2] == step_size_array[1])):
                print("no convergence, set min. step size.")
                # if no convergence, switch to minumum step size
                dt = min_step_size

        # else, all is good, continue with current step size
        while (envelope_left == True):
            # go through RK iterations as long as there is envelope left

            # apply Runge Kutta 4th order to find next value of M_dot
            # NOTE: the mass lost in one timestep is in Earth masses
            Mdot1 = mass_loss_rate_forward_Ot20(t, epsilon, K_on, beta_on,
                                                planet_object, R, track_dict)
            k1 = (dt * Myr_to_sec * Mdot1) / M_EARTH
            M_k1 = M + 0.5 * k1  # mass after 1st RK step
            R_k1 = plmoOt20.calculate_radius_planet_Ot20(M_k1)
            if (i == 1) and (j == 1) and (M_k1 < M_core):
                # then I am still in the first RK iteration, and the initial
                # step size was likely too large -> set step size to minumum
                dt = min_step_size
                j += 1
                break

            Mdot2 = mass_loss_rate_forward_Ot20(t + 0.5 * dt, epsilon, K_on,
                                                beta_on, planet_object, R_k1,
                                                track_dict)
            k2 = (dt * Myr_to_sec * Mdot2) / M_EARTH
            M_05k2 = M + 0.5 * k2
            R_05k2 = plmoOt20.calculate_radius_planet_Ot20(M_05k2)
            if (i == 1) and (j == 1) and (M_05k2 < M_core):
                dt = min_step_size
                j += 1
                break

            Mdot3 = mass_loss_rate_forward_Ot20(t + 0.5 * dt, epsilon, K_on,
                                                beta_on, planet_object, R_05k2,
                                                track_dict)
            k3 = (dt * Myr_to_sec * Mdot3) / M_EARTH
            M_05k3 = M + k3
            R_05k3 = plmoOt20.calculate_radius_planet_Ot20(M_05k3)
            if (i == 1) and (j == 1) and (M_05k3 < M_core):
                dt = min_step_size
                j += 1
                break

            Mdot4 = mass_loss_rate_forward_Ot20(t + dt, epsilon, K_on, beta_on,
                                                planet_object, R_05k3,
                                                track_dict)
            k4 = (dt * Myr_to_sec * Mdot4) / M_EARTH
            # total mass lost after time-step dt
            M_lost = (k1 + 2 * k2 + 2 * k3 + k4) / 6.

            # update next value of the planet mass
            M_new = M + M_lost

            # now it is time to check if atmosphere is gone or
            # if planet is close to complete atmosphere removal
            if ((np.isnan(M_lost) == True) \
                    or (np.iscomplex(M_new) == True)) \
                    and (dt == min_step_size):
                # if M_lost = nan (i.e. planet evaporates in one of the RK
                # steps) OR the four RK steps finish and the new planet mass is
                # smaller or equal to the core mass, then the planet counts as
                # evaporated!
                # if this condition is satisfied and the step size is already
                # at a minimum, then we assume the current RK iteration would
                # remove all atmosphere and only the rocky core is left at
                # t_i+dt; this terminates the code and returns the final planet
                # properties

                #print("Atmosphere has evaportated! Only bare rocky core left!"\
                #       + " STOP this madness!")

                # since the the stop criterium is reached, we assume at t_i+1
                # the planet only consists of the bare rocky core with the
                # planet mass equal to the core mass and the planet radius
                # equal to the core radius
                t_arr.append(t_arr[-1] + dt)
                M_arr.append(M_core)
                R_arr.append(R_core)
                Lx_arr.append(lx_evo(t=t_arr[-1] + dt, track_dict=track_dict))
                envelope_left = False  # set flag for complete env. removal
                j += 1
                break

            elif ((np.isnan(M_lost) == True) \
                    or (np.iscomplex(M_new) == True)) \
                    and (dt > min_step_size) \
                    and (close_to_evaporation == False):
                #print("close to evaporation")
                # planet close to evaporation, but since the step size is not
                # minimum yet, we set it to its minimum value and run the RK
                # iteration again (until above stopping condition is fulfilled)
                dt = min_step_size
                close_to_evaporation = True
                # this variable is for making sure the code does not run into
                # an infinite loop when the planet is close to evaporation.
                # Once this condition is set to True, the code continues with
                # a fixed min. step size and is no longer allowed to adjust it.
                j += 1
                break

            # this part is new compared to the one used in the PAPER (there we
            # used a fixed step size!)
            # if you're still in the while loop at this point, then calculate
            # new radius and check how drastic the radius change would be;
            # adjust the step size if too drastic or too little
            R_new = plmoOt20.calculate_radius_planet_Ot20(M_new)
            #print("R_new, f_new: ", R_new, f_env_new)
            #print(abs((R-R_new)/R)*100)

            # only adjust step size if planet is not close to complete evaporat.
            if (close_to_evaporation == False):
                #print("radius check")
                # then do the check on how much the radius changes
                # R(t_i) compared to R(t_i+dt);
                # if radius change is larger than 0.5%, make step size smaller
                # by factor 10 OR if radius change is smaller than 0.02%, make
                # step size bigger by factor 10, but not bigger or smaller than
                # max. or min. step size!
                # if radius change too much/little, do not write anything to
                # file, instead do RK iteration again with new step size

                R_change = abs(
                    (R - R_new) / R) * 100  # radius change compared to
                # previous radius - in percent
                if (R_change > 1.) \
                        and (t < track_dict["t_curr"]) \
                        and (dt > min_step_size):
                    dt = dt / 10.
                    j += 1
                    break

                #elif ((R_change < 1.) \
                #           and (R_change >=0.1)) \
                #           and (t < track_dict["t_curr"]) \
                #           and (dt > min_step_size):
                #    dt = dt / 10.
                #    break

                elif (R_change < (0.01)) \
                        and (t < track_dict["t_curr"]) \
                        and (dt < max_step_size):
                    dt = dt * 10.
                    j += 1
                    break

                # NOTE: in principle I can adjust the code such that these
                # hardcoded parameters are different for early planet evolution
                # where much more is happening typically, and late planet
                # evolution where almost no change is occurring anymore
                elif (R_change > 1.) \
                        and (t >= track_dict["t_curr"]) \
                        and (dt > min_step_size):
                    dt = dt / 10.
                    j += 1
                    break

                elif (R_change < (0.01)) \
                        and (t >= track_dict["t_curr"]) \
                        and (dt < max_step_size):
                    dt = dt * 10
                    j += 1
                    break

                else:  # if radius change is ok
                    # do sanity check: is new planet mass is still greater than
                    # the core mass? ->then there is still some atmosphere left
                    # in this case update params and go into next RK iteration
                    if ((M + M_lost) - M_core) > 0:
                        M = M + M_lost  # new planet mass (M_lost is negative)
                        t = t_arr[-1] + dt  # updated time value t_i_plus_1
                        M_arr.append(M)
                        t_arr.append(t)
                        Lx_arr.append(lx_evo(t=t, track_dict=track_dict))

                        # calculate new envelope mass fraction:
                        M_env = M - M_core
                        f_env = (M_env / M) * 100  # in %

                        # calculate new radius with new planet mass
                        # one time step later
                        R = plmoOt20.calculate_radius_planet_Ot20(M)
                        R_arr.append(R)
                        i += 1  # update step to i+1
                        j += 1

                    else:
                        # this should never happen
                        sys.exit('sth went wrong of you see this!')
                break

            elif (close_to_evaporation == True):
                # if this condition is true, do not adjust step size
                # based on the radius change
                if ((M + M_lost) - M_core) > 0:
                    M = M + M_lost  # new planet mass (M_lost is negative)
                    t = t_arr[-1] + dt  #  updated time value t_i_plus_1
                    M_arr.append(M)
                    t_arr.append(t)
                    Lx_arr.append(lx_evo(t=t, track_dict=track_dict))

                    # calculate new envelope mass fraction:
                    M_env = M - M_core
                    f_env = (M_env / M) * 100  # in %

                    # calculate new radius with new planet mass
                    # one time step later
                    R = plmoOt20.calculate_radius_planet_Ot20(M)
                    R_arr.append(R)
                    i += 1  # update step to i+1
                    j += 1

                else:
                    sys.exit('sth went wrong of you see this!')
            break

        if (envelope_left == False):
            # planet has evaporated, so return last planet params for bare core
            return np.array(t_arr), np.array(M_arr), \
                   np.array(R_arr), np.array(Lx_arr)

    #print("Done!")
    return np.array(t_arr), np.array(M_arr), \
           np.array(R_arr), np.array(Lx_arr)
Example #4
0
def mass_planet_RK4_forward_Ot20_PAPER(epsilon, K_on, beta_on, planet_object,
                                       initial_step_size, t_final, track_dict):
    """USED: 4th order Runge-Kutta as numerical integration  method 
    Integrate from the current time (t_start (where planet has R0 and M0)
    into the future taking into account photoevaporative mass loss. 

    Input:
    ----------
    epsilon: evaporation efficiency
    K_on: set use of K parameter on or off 
    beta_on: set use of beta parameter on or off
    planet_object: object of planet class which contains also stellar
                   parameters and info about stellar evo track
    step_size: initial step_size, fixed
    t_final: final time of simulation
    track_dict: dictionary with Lx evolutionary track parameters
    
    Output:
    ----------
    t_arr, M_arr, R_arr, Lx_arr: array of time, mass, radius and 
    							 Lx values from t_start to t_final
    """

    M_EARTH = const.M_earth.cgs.value
    Myr_to_sec = 1e6 * 365 * 86400

    # initialize the starting values for Lxuv(t_start), mass, density, beta, K
    Lx0 = lx_evo(t=track_dict["t_start"], track_dict=track_dict)
    Lxuv0 = l_xuv_all(Lx0)
    Fxuv0 = flux_at_planet(Lxuv0, planet_object.distance)

    # "make" initial planet at t_start
    R0 = R = planet_object.radius
    M0 = M = planet_object.mass
    rho0 = rho = plmoOt20.density_planet(M0, R0)  # initial approx. density

    # specify beta0 and K0
    if beta_on == "yes":
        beta = beta0 = bk.beta_fct(M0, Fxuv0, R0)
    elif beta_on == "no":
        beta = beta0 = 1.
    if K_on == "yes":
        K = K0 = bk.K_fct(planet_object.distance, M0, planet_object.mass_star,
                          R0)
    elif K_on == "no":
        K = K0 = 1.

    # create time array for integration (with user-specified step size)
    t_start, t_max = track_dict["t_start"], t_final
    step_size = initial_step_size
    number = math.ceil((t_max - t_start) / step_size)
    times, step_size2 = np.linspace(t_start,
                                    t_max,
                                    number,
                                    endpoint=True,
                                    retstep=True)
    dt = step_size2

    # make lists of all the values wwe want to track & output in the end:
    M_arr = []
    M_arr.append(M0)
    R_arr = []
    R_arr.append(R0)
    t_arr = []
    t_arr.append(t_start)
    Lx_arr = []
    Lx_arr.append(Lx0)

    # CRITERION when to stop the mass-loss
    R_core = 2.15  # stop when this radius is reached! (this is the minimum
    # radius for which the volatile regime is valid)
    M_core = plmoOt20.calculate_mass_planet_Ot20(R_core)

    for i in range(0, len(times) - 1):
        # this is just for me to return the Lx(t) evolution to check if it is
        # correct (not required since the Lx(t) calculation is embedded in
        # the mass_loss_rate_fancy function)
        Lx_i = lx_evo(t=t_arr[i], track_dict=track_dict)

        Mdot1 = mass_loss_rate_forward_Ot20(times[i], epsilon, K_on, beta_on,
                                            planet_object, R, track_dict)
        k1 = (dt * Myr_to_sec * Mdot1) / M_EARTH  # in earth masses
        M_05k1 = M + 0.5 * k1
        R_05k1 = plmoOt20.calculate_radius_planet_Ot20(M_05k1)

        Mdot2 = mass_loss_rate_forward_Ot20(times[i] + 0.5 * dt, epsilon, K_on,
                                            beta_on, planet_object, R_05k1,
                                            track_dict)
        k2 = (dt * Myr_to_sec * Mdot2) / M_EARTH
        M_05k2 = M + 0.5 * k2
        R_05k2 = plmoOt20.calculate_radius_planet_Ot20(M_05k2)

        Mdot3 = mass_loss_rate_forward_Ot20(times[i] + 0.5 * dt, epsilon, K_on,
                                            beta_on, planet_object, R_05k2,
                                            track_dict)
        k3 = (dt * Myr_to_sec * Mdot3) / M_EARTH
        M_05k3 = M + 0.5 * k3
        R_05k3 = plmoOt20.calculate_radius_planet_Ot20(M_05k3)

        Mdot4 = mass_loss_rate_forward_Ot20(times[i] + dt, epsilon, K_on,
                                            beta_on, planet_object, R_05k3,
                                            track_dict)
        k4 = (dt * Myr_to_sec * Mdot4) / M_EARTH
        M_lost = (k1 + 2 * k2 + 2 * k3 +
                  k4) / 6.  # mass lost after time-step dt

        # check if planet with new mass does still have some atmosphere
        if ((M + M_lost) - M_core) > 0:
            # then planet still has some atmosphere left -> continue
            M = M + M_lost  # new planet mass (M_lost is negative)
            # t_i_plus_1 - update time value
            t = t_arr[-1] + dt
            # calculate new radius with new planet mass
            R = plmoOt20.calculate_radius_planet_Ot20(M)
            M_arr.append(M)
            R_arr.append(R)
            t_arr.append(t)
            Lx_arr.append(lx_evo(t=t, track_dict=track_dict))

        else:
            # all atmosphere is gone (based on criterion set at the top)
            #print("Atmosphere has evaportated! Only bare rocky core" \
            #	 + "left! STOP this madness!")

            # if the stop criterium is reached, I add the core mass
            # and core radius to the array at t_i+1

            t_arr.append(t_arr[-1] + dt)
            M_arr.append(M_core)
            R_arr.append(R_core)
            Lx_arr.append(lx_evo(t=t_arr[-1] + dt, track_dict=track_dict))
            return np.array(t_arr), np.array(M_arr), \
                   np.array(R_arr), np.array(Lx_arr)

    # if planet survives, output the final arrays
    #Lx_arr[i+1] = lx_evo(t=t_arr[-1]+dt, track_dict=track_dict)
    #Lx_arr.append(lx_evo(t=t_arr[-1]+dt, track_dict=track_dict))
    #print("Done!")
    return np.array(t_arr), np.array(M_arr), \
           np.array(R_arr), np.array(Lx_arr)
Example #5
0
def mass_planet_RK4_forward_LO14_PAPER(epsilon, K_on, beta_on, planet_object,
                                       initial_step_size, t_final, track_dict):
    """USED: 4th order Runge-Kutta as numerical integration  method 
    Integrate from the current time (t_start (where planet has R0 and M0) 
    into the future taking into account photoevaporative mass loss. 
    Step size is fixed!

    Parameters:
    -----------
    epsilon (float): evaporation efficiency
    K_on (str): set use of K parameter on or off ("on" or "off)
    beta_on (str): set use of beta parameter on or off ("on" or "off)
    planet_object: object of planet class which contains also stellar 
                   parameters and info about stellar evo track
    step_size (float): fixed
    t_final (float): final time of simulation
    track_dict (dict): dictionary with Lx evolutionary track parameters

    Returns:
    --------
    t_arr (array): time array to trace mass and radius evolution
    M_arr (array): mass array with mass evolution over time (mass decrease)
    R_arr (array): radius array with radius evolution over time (from
                   thermal contraction and photoevaporative mass-loss)
    Lx_arr (array): array to trace the X-ray luminosity (mainly for
                    consistency checks)
    """

    M_EARTH = const.M_earth.cgs.value
    Myr_to_sec = 1e6 * 365 * 86400

    # initialize the starting values for Lxuv(t_start), mass, density, beta, K
    Lx0 = lx_evo(t=track_dict["t_start"], track_dict=track_dict)
    Lxuv0 = l_xuv_all(Lx0)
    Fxuv0 = flux_at_planet(Lxuv0, planet_object.distance)

    # initial planet parameters at t_start
    f_env_0 = f_env = planet_object.fenv
    R0 = R = planet_object.radius
    M0 = M = planet_object.mass
    rho0 = rho = plmoLoFo14.density_planet(M0, R0)  # initial mean density
    M_env0 = M_env = M0 - planet_object.core_mass  # initial envelope mass
    M_core = planet_object.core_mass

    # specify beta0 and K0
    if beta_on == "yes":
        beta = beta0 = bk.beta_fct(M0, Fxuv0, R0)
    elif beta_on == "no":
        beta = beta0 = 1.
    if K_on == "yes":
        K = K0 = bk.K_fct(planet_object.distance, M0, planet_object.mass_star,
                          R0)
    elif K_on == "no":
        K = K0 = 1.

    t_start = track_dict["t_start"]
    t_max = t_final
    step_size = initial_step_size

    # create time array for integration (with user-specified step size)
    number = math.ceil((t_max - t_start) / step_size)
    times, step_size2 = np.linspace(t_start,
                                    t_max,
                                    number,
                                    endpoint=True,
                                    retstep=True)
    dt = step_size2

    # here I make lists of all the values I would like to
    # track & output in the end:
    M_arr = []
    M_arr.append(M0)
    R_arr = []
    R_arr.append(R0)
    t_arr = []
    t_arr.append(t_start)
    Lx_arr = []
    Lx_arr.append(Lx0)

    # CRITERION when to stop the mass-loss
    # the LofO14 planets have a FIXED core mass and thus core radius
    # (bare rocky core)
    R_core = planet_object.core_radius  # stop when this radius is reached!

    for i in range(0, len(times) - 1):
        #print(t_arr[i])

        # this is just for me to return the Lx(t) evolution to check if
        # it is correct (not required since the Lx(t)
        # calculation is embedded in the mass_loss_rate_fancy function)
        Lx_i = lx_evo(t=t_arr[i], track_dict=track_dict)

        Mdot1 = mass_loss_rate_forward_LO14(times[i], epsilon, K_on, beta_on,
                                            planet_object, f_env, track_dict)
        k1 = (dt * Myr_to_sec * Mdot1) / M_EARTH  # mass lost in one timestep
        # in earth masses
        M_05k1 = M + 0.5 * k1
        M_env_05k1 = M_05k1 - M_core
        f_env_05k1 = (M_env_05k1 / M_05k1) * 100

        Mdot2 = mass_loss_rate_forward_LO14(times[i] + 0.5 * dt, epsilon, K_on,
                                            beta_on, planet_object, f_env_05k1,
                                            track_dict)
        k2 = (dt * Myr_to_sec * Mdot2) / M_EARTH
        M_05k2 = M + 0.5 * k2
        M_env_05k2 = M_05k2 - M_core
        f_env_05k2 = (M_env_05k2 / M_05k2) * 100

        Mdot3 = mass_loss_rate_forward_LO14(times[i] + 0.5 * dt, epsilon, K_on,
                                            beta_on, planet_object, f_env_05k2,
                                            track_dict)
        k3 = (dt * Myr_to_sec * Mdot3) / M_EARTH
        M_k3 = M + k3
        M_env_k3 = M_k3 - M_core
        f_env_k3 = (M_env_k3 / M_k3) * 100

        Mdot4 = mass_loss_rate_forward_LO14(times[i] + dt, epsilon, K_on,
                                            beta_on, planet_object, f_env_k3,
                                            track_dict)
        k4 = (dt * Myr_to_sec * Mdot4) / M_EARTH
        M_lost = (k1 + 2 * k2 + 2 * k3 + k4) / 6.  # after time-step dt

        # check if planet with new mass does still have some atmosphere
        if ((M + M_lost) - M_core) > 0:
            # then planet still has some atmosphere left -> continue
            M = M + M_lost  # new planet mass
            M_env = M - M_core  # new envelope mass
            t = t_arr[i] + dt  # t_i_plus_1 - update time value
            f_env = (M_env / M) * 100  # in %
            # calculate new radius with new planet mass/envelope mass
            # fraction & one time step later
            R = plmoLoFo14.calculate_planet_radius(
                M_core, f_env, t,
                flux_at_planet_earth(planet_object.Lbol,
                                     planet_object.distance),
                planet_object.metallicity)
            t_arr.append(t)
            M_arr.append(M)
            R_arr.append(R)
            Lx_arr.append(lx_evo(t=t, track_dict=track_dict))

        else:
            # all atmosphere is gone -> terminate
            #print("Atmosphere has evaportated! Only bare rocky" \
            # 	  + "core left! STOP this madness!")

            # if the stop criterium is reached, I add the core mass
            # and core radius to the array at t_i+1
            t_arr.append(t_arr[-1] + dt)
            M_arr.append(M_core)
            R_arr.append(R_core)
            Lx_arr.append(lx_evo(t=t_arr[-1] + dt, track_dict=track_dict))
            return np.array(t_arr), np.array(M_arr), \
                   np.array(R_arr), np.array(Lx_arr)

    #print("Done!")
    return np.array(t_arr), np.array(M_arr), \
           np.array(R_arr), np.array(Lx_arr)