Beispiel #1
0
def generate_stars(T_c_values=np.linspace(4 * million_K, 40 * million_K, 20),
                   config=StellarConfiguration(),
                   file_name=None,
                   threads=4):
    """
    Generates and saves aggregated stellar data for the given list of central temperature values.

    :param T_c_values: The central temperature values of the stars to generate.
    :param config: The StellarConfiguration to use.
    :param file_name: The file name to save the aggregated stellar data.
    :param threads: The number of threads to use.
    :return: The aggregated stellar data.
    """
    if threads > 1:
        params = [(T_c_values[i * len(T_c_values) // threads:(i + 1) *
                              len(T_c_values) // threads], config)
                  for i in range(threads)]
        pool = Pool(threads)
        stellar_data_segments = pool.starmap(calculate_stellar_data, params)
        stellar_data = np.column_stack(tuple(stellar_data_segments))
    else:
        stellar_data = calculate_stellar_data(T_c_values, config)

    save_stellar_data(stellar_data, file_name)
    return stellar_data
Beispiel #2
0
def calculate_stellar_data(T_c_values, config=StellarConfiguration()):
    """
    Calculates aggregated stellar data for the given central temperature values.
    This function does not use multiprocessing.

    :param T_c_values: The list of central Temperatures to calculate stellar data for.
    :param config: The stellar configuration to use.
    :return:
    """
    stellar_data = None
    for i, T_c in enumerate(T_c_values):
        print(i)
        if i == 0:
            error, r_values, state_values = solve_bvp(T_c)
            stellar_data = get_aggregated_data(r_values,
                                               state_values)[:, np.newaxis]
            continue

        rho_c_guess = predict_rho_c(T_c, stellar_data[T_c_index, :],
                                    stellar_data[rho_c_index, :])
        error, r_values, state_values = solve_bvp(T_c,
                                                  rho_c_guess=rho_c_guess,
                                                  config=config)
        stellar_data = np.column_stack(
            (stellar_data, get_aggregated_data(r_values, state_values)))
        print("Expected:", rho_c_guess, ", Actual:", state_values[rho_index,
                                                                  0])
    return stellar_data
Beispiel #3
0
def trial_solution(rho_c,
                   T_c,
                   r_0=100 * m,
                   rtol=1e-9,
                   atol=None,
                   return_star=False,
                   optical_depth_threshold=1e-4,
                   mass_threshold=1000 * M_sun,
                   config=StellarConfiguration()):
    """
    Integrates the state of the star from r_0 until the estimated optical depth is below the given threshold.
    The array of radius values and the state matrix are returned along with the fractional surface luminosity error.

    :param rho_c: The central density.
    :param T_c: The central temperature.
    :param r_0: The starting value of the radius. Must be greater than 0 to prevent numerical instabilities.
                Defaults to 100m.
    :param return_star: If True, then the radius values and state matrix will be returned alongside
                        the fractional surface luminosity error.
    :param rtol: The required relative accuracy during integration.
    :param atol: The required absolute accuracy during integration. Defaults to rtol / 1000.
    :param optical_depth_threshold: The value below which the estimated remaining optical depth
                                    must drop for the integration to terminate.
    :param mass_threshold: If the mass of the star increases beyond this value, then integration will be halted.
                           Defaults to 1000 solar masses.
    :param config: The stellar configuration to use.
    :returns: The fractional surface luminosity error, and optionally the array of radius values and the state matrix
    """
    print(rho_c)
    if atol is None:
        atol = rtol / 1000

    # Event based end condition
    def halt_integration(r, state):
        if state[M_index] > mass_threshold:
            return -1
        return get_remaining_optical_depth(
            r, state, config=config) - optical_depth_threshold

    halt_integration.terminal = True

    # Ending radius is infinity, integration will only be halted via the halt_integration event
    # Not sure what good values for atol and rtol are, but these seem to work well
    result = integrate.solve_ivp(config.get_state_derivative, (r_0, np.inf),
                                 config.get_initial_conditions(rho_c,
                                                               T_c,
                                                               r_0=r_0),
                                 events=halt_integration,
                                 atol=atol,
                                 rtol=rtol)
    # noinspection PyUnresolvedReferences
    r_values, state_values = result.t, result.y

    return truncate_star(r_values, state_values, return_star=return_star)
Beispiel #4
0
def get_remaining_optical_depth(r,
                                state,
                                kappa_value=None,
                                rho_prime_value=None,
                                config=StellarConfiguration()):
    """
    Calculates an estimate of the remaining optical depth from the current radius to infinity.
    Once this value is sufficiently small, integration can be terminated and the value of optical depth can be assumed
    to be approximately equivalent to the optical depth at infinity.

    :param r: The current radius.
    :param state: The current state vector.
    :param kappa_value: The current optical depth. Can optionally be provided to prevent redoing the calculation.
    :param rho_prime_value: The current derivative of density (with respect to radius).
                            Can optionally be provided to prevent redoing the calculation.
    :param config: The stellar configuration to use.
    """
    rho, T, M, L, _ = state
    if kappa_value is None:
        kappa_value = config.kappa(rho, T)
    if rho_prime_value is None:
        rho_prime_value = config.rho_prime(r, rho, T, M, L)
    return kappa_value * rho**2 / np.abs(rho_prime_value)
Beispiel #5
0
def solve_bvp(T_c,
              rho_c_guess=100 * g / cm**3,
              confidence=0.9,
              rho_c_min=0.3 * g / cm**3,
              rho_c_max=4e6 * g / cm**3,
              rho_c_tol=1e-20 * kg / m**3,
              rtol=1e-11,
              optical_depth_threshold=1e-4,
              config=StellarConfiguration()):
    """
    Solves for the structure of a star with the given central temperature using the point and shoot method.

    This uses the bisection algorithm, with a modification based on confidence. The higher the confidence, the quicker
    convergence will be to rho_c_guess. Once rho_c_guess falls outside the interval of interest,
    simple bisection is used. Too low of a confidence will cause this to reduce to simple bisection, and too high of a
    confidence will likely cause rho_c_guess to fall outside the range of interest too fast leaving an unnecessarily
    large remaining search space.

    :param T_c: The central temperature.
    :param rho_c_guess: A guess for the central density.
    :param confidence: The confidence of the guess. Must be between 0.5 (no confidence) and 1 (perfect confidence).
    :param rho_c_min: The minimum possible central density.
    :param rho_c_max: The maximum possible central density.
    :param rho_c_tol: The tolerance within which the central density must be determined for integration to end.
    :param rtol: The rtol to use.
    :param optical_depth_threshold: The optical_depth_threshold to use.
    :param config: The stellar configuration to use.
    :return: The resulting fractional luminosity error, r_values and state_values of the converged stellar solution.
    """
    args = dict(T_c=T_c,
                rtol=rtol,
                optical_depth_threshold=optical_depth_threshold,
                config=config)
    if confidence < 0.5:
        raise Exception("Confidence must be at least 0.5!")
    if confidence >= 1:
        raise Exception("Confidence must be less than 1!")

    y_guess = trial_solution(rho_c_guess, **args)
    if y_guess == 0:
        return rho_c_guess

    y0 = trial_solution(rho_c_min, **args)
    if y0 == 0:
        return rho_c_min
    if y0 < 0 < y_guess:
        rho_c_low, rho_c_high = rho_c_min, rho_c_guess
        bias_high = True
        bias_low = False
    elif y_guess < 0 < y0:
        rho_c_low, rho_c_high = rho_c_guess, rho_c_min
        bias_low = True
        bias_high = False
    else:
        y1 = trial_solution(rho_c_max, **args)
        if y1 == 0:
            return rho_c_max
        if y1 < 0 < y_guess:
            rho_c_low, rho_c_high = rho_c_max, rho_c_guess
            bias_high = True
            bias_low = False
        elif y_guess < 0 < y1:
            rho_c_low, rho_c_high = rho_c_guess, rho_c_max
            bias_low = True
            bias_high = False
        else:
            print("Retrying with larger rho_c interval for", T_c)
            # set confidence to be much higher since we know that the other boundary will be even further from the guess
            return solve_bvp(T_c,
                             rho_c_guess,
                             confidence=(confidence + 4) / 5,
                             rho_c_min=rho_c_min / 1000,
                             rho_c_max=1000 * rho_c_max,
                             rho_c_tol=rho_c_tol,
                             rtol=rtol,
                             optical_depth_threshold=optical_depth_threshold,
                             config=config)

    while np.abs(rho_c_high - rho_c_low) / 2 > rho_c_tol:
        if bias_low:
            rho_c_guess = confidence * rho_c_low + (1 -
                                                    confidence) * rho_c_high
            if rho_c_guess == rho_c_low or rho_c_guess == rho_c_high:
                print('Reached limits of numerical precision for rho_c')
                break
            y_guess = trial_solution(rho_c_guess, **args)
            if y_guess == 0:
                return rho_c_guess
            if y_guess < 0:
                rho_c_low = rho_c_guess
                bias_low = False  # ignore initial guess bias now that it is no longer the low endpoint
            elif y_guess > 0:
                rho_c_high = rho_c_guess
        elif bias_high:
            rho_c_guess = (1 -
                           confidence) * rho_c_low + confidence * rho_c_high
            if rho_c_guess == rho_c_low or rho_c_guess == rho_c_high:
                print('Reached limits of numerical precision for rho_c')
                break
            y_guess = trial_solution(rho_c_guess, **args)
            if y_guess == 0:
                return rho_c_guess
            if y_guess < 0:
                rho_c_low = rho_c_guess
            elif y_guess > 0:
                rho_c_high = rho_c_guess
                bias_high = False  # ignore initial guess bias now that it is no longer the high endpoint
        else:
            rho_c_guess = (rho_c_low + rho_c_high) / 2
            if rho_c_guess == rho_c_low or rho_c_guess == rho_c_high:
                print('Reached limits of numerical precision for rho_c')
                break
            y_guess = trial_solution(rho_c_guess, **args)
            if y_guess == 0:
                return rho_c_guess
            if y_guess < 0:
                rho_c_low = rho_c_guess
            elif y_guess > 0:
                rho_c_high = rho_c_guess

    rho_c = (rho_c_high + rho_c_low) / 2

    # if solution failed to converge, recurse with greater accuracy
    if np.abs(y_guess) > 1000:
        print('Retrying with higher accuracy for', T_c)
        return solve_bvp(
            T_c,
            rho_c,
            confidence=0.99,  # confidence is extremely high now
            rho_c_min=rho_c_min,
            rho_c_max=rho_c_max,
            rho_c_tol=rho_c_tol,
            rtol=rtol * 100,
            optical_depth_threshold=optical_depth_threshold)

    # Generate and return final star
    return trial_solution(rho_c,
                          T_c,
                          return_star=True,
                          rtol=rtol,
                          optical_depth_threshold=optical_depth_threshold)
import numpy as np
from src.stellar_structure_equations import StellarConfiguration
from src.numerical_integration import solve_bvp
from src.units import R_sun, million_K, g, cm
from src.graphing import graph_star
from src.example_star import load_example_data
from src.star_sequence_generator import generate_stars


if __name__ == '__main__':
    reference_data = load_example_data(file_name='../Example Stars/high_mass_star.txt')
    config = StellarConfiguration()
    error, r_values, state_values = solve_bvp(20 * million_K,
                                              rho_c_guess=80.063 * g / cm ** 3,
                                              confidence=0.5,
                                              config=config)
    print('Error', error)
    graph_star(r_values, state_values, 'high_mass', config=config, reference_data=reference_data)
Beispiel #7
0
def test_stellar_structure_equations(
    file_name="../Example Stars/low_mass_star.txt",
    config=StellarConfiguration()):
    data = np.loadtxt(file_name).T * example_star_units[:, None]

    diff = config.T_prime_radiative(data[ex_r_index, :], data[ex_rho_index, :], data[ex_T_index, :],
                                    data[ex_L_index, :]) - \
        config.T_prime_convective(data[ex_r_index, :], data[ex_rho_index, :], data[ex_T_index, :],
                                  data[ex_M_index, :])

    rho_prime_actual = config.rho_prime(data[ex_r_index, :],
                                        data[ex_rho_index, :],
                                        data[ex_T_index, :],
                                        data[ex_M_index, :],
                                        data[ex_L_index, :])
    rho_prime_expected = data[ex_rho_prime_index, :]
    print(
        "Rho Prime Percentage Error:",
        stats.describe(
            (rho_prime_actual - rho_prime_expected) / rho_prime_expected))

    T_prime_actual = config.T_prime(data[ex_r_index, :], data[ex_rho_index, :],
                                    data[ex_T_index, :], data[ex_M_index, :],
                                    data[ex_L_index, :])
    T_prime_expected = data[ex_T_prime_index, :]
    print(
        "T Prime Percentage Error:",
        stats.describe((T_prime_actual - T_prime_expected) / T_prime_expected))

    M_prime_actual = config.M_prime(data[ex_r_index, :], data[ex_rho_index, :])
    M_prime_expected = data[ex_M_prime_index, :]
    print(
        "M PrimePercentage Error:",
        stats.describe((M_prime_actual - M_prime_expected) / M_prime_expected))

    L_prime_actual = config.L_prime(data[ex_r_index, :], data[ex_rho_index, :],
                                    data[ex_T_index, :])
    L_prime_expected = data[ex_L_prime_index, :]
    print(
        "L prime Percentage Error:",
        stats.describe((L_prime_actual - L_prime_expected) / L_prime_expected))

    P_actual = config.P(data[ex_rho_index, :], data[ex_T_index, :])
    P_expected = data[ex_P_index, :]
    print("P Percentage Error:",
          stats.describe((P_actual - P_expected) / P_expected))

    P_degeneracy_actual = config.P_degeneracy(data[ex_rho_index, :])
    P_degeneracy_expected = data[ex_P_degeneracy_index, :]
    print(
        "P_degeneracy Percentage Error:",
        stats.describe((P_degeneracy_actual - P_degeneracy_expected) /
                       P_degeneracy_expected))

    P_gas_actual = config.P_gas(data[ex_rho_index, :], data[ex_T_index, :])
    P_gas_expected = data[ex_P_gas_index, :]
    print("P_gas Percentage Error:",
          stats.describe((P_gas_actual - P_gas_expected) / P_gas_expected))

    kappa_actual = config.kappa(data[ex_rho_index, :], data[ex_T_index, :])
    kappa_expected = data[ex_kappa_index, :]
    print("Kappa Percentage Error:",
          stats.describe((kappa_actual - kappa_expected) / kappa_expected))
def get_convective_regions(r_values, state_values, surface_r, kappa_total_values=None, config=StellarConfiguration()):
    if kappa_total_values is None:
        kappa_total_values = config.kappa(state_values[rho_index, :], state_values[T_index, :])
    convective = config.is_convective(r_values, state_values[rho_index, :],
                                      state_values[T_index, :], state_values[M_index, :],
                                      state_values[L_index, :], kappa_value=kappa_total_values)
    indices = iter(np.concatenate(([0], np.argwhere(np.diff(convective))[:, 0], [len(convective) - 1])))
    if not convective[0]:
        next(indices)  # remove first
    pairs = []
    while True:
        try:
            a = next(indices)
            b = next(indices)
            pairs.append((r_values[a] / surface_r, r_values[b] / surface_r))
        except StopIteration:
            return pairs
def graph_star(r_values, state_values, name=None, reference_data=None, config=StellarConfiguration()):
    """

    :param r_values: The radius values of this star.
    :param state_values: The corresponding state matrix of this star.
    :param name: The file name prefix for this star's graphs to be saved with.
    :param reference_data: If provided, it will be graphed with dashed lines with the same scale as the stellar data.
    :param config: The stellar configuration that was used to generate this star.
    """
    if name is None:
        name = 'test_star_' + get_timestamp()

    title_description = ' (' + config.describe_gravity_modifications() + ')' \
        if config.has_gravity_modifications() else ''

    surface_r = r_values[-1]
    surface_state = state_values[:, -1]
    rho_c = state_values[rho_index, 0]
    T_c = state_values[T_index, 0]
    surface_M = surface_state[M_index]
    surface_L = surface_state[L_index]
    surface_T = surface_state[T_index]

    print("Central Density:", rho_c / (g / cm ** 3), r"$\frac{g}{cm^3}$")
    print("Central Temperature:", T_c / million_K, "million K")
    print("Radius:", surface_r / R_sun, r"$R_{sun}$")
    print("Mass:", surface_M / M_sun, r"$M_{sun}$")
    print("Luminosity:", surface_L / L_sun, r"$L_{sun}$")
    print("Surface Temperature:", surface_T / K, "K")

    r_graph_values = r_values / surface_r

    # Compute convective regions
    kappa_total_values = config.kappa(state_values[rho_index, :], state_values[T_index, :])
    convective_regions = get_convective_regions(r_values, state_values, surface_r, kappa_total_values, config=config)

    def mark_convective_regions(facecolor='gray', alpha=0.2):
        for a, b in convective_regions:
            plt.axvspan(a, b, facecolor=facecolor, alpha=alpha)

    plt.plot(r_graph_values, state_values[rho_index, :] / rho_c, label=r"$\rho$", color="black")
    plt.plot(r_graph_values, state_values[T_index, :] / T_c, label="T", color="red")
    plt.plot(r_graph_values, state_values[M_index, :] / surface_M, label="M", color="green")
    plt.plot(r_graph_values, state_values[L_index, :] / surface_L, label="L", color="blue")
    mark_convective_regions()
    if reference_data is not None:
        r_ref_values = reference_data[ex_r_index, :] / surface_r
        plt.plot(r_ref_values, reference_data[ex_rho_index, :] / rho_c,
                 label=r"$\rho_{ref}$", color="black", linestyle='dashed')
        plt.plot(r_ref_values, reference_data[ex_T_index, :] / T_c,
                 label=r"$T_{ref}$", color="red", linestyle='dashed')
        plt.plot(r_ref_values, reference_data[ex_M_index, :] / surface_M,
                 label=r"$M_{ref}$", color="green", linestyle='dashed')
        plt.plot(r_ref_values, reference_data[ex_L_index, :] / surface_L,
                 label=r"$L_{ref}$", color="blue", linestyle='dashed')

    plt.legend()
    plt.xlim(0, 1)
    plt.ylim(0, 1)
    plt.title(r"Properties Versus Radius Fraction" + title_description)
    plt.xlabel(r"$r/R_{star}$")
    plt.ylabel(r"$\frac{\rho}{\rho_{c}}, \frac{T}{T_{c}}, \frac{M}{M_{star}}, \frac{L}{L_{star}}$")
    plt.savefig("../Graphs/" + name + "_properties.png")
    plt.clf()

    P_degeneracy_values = config.P_degeneracy(state_values[rho_index, :])
    P_gas_values = config.P_gas(state_values[rho_index, :], state_values[T_index, :])
    P_photon_values = config.P_photon(state_values[T_index, :])
    P_total_values = P_degeneracy_values + P_gas_values + P_photon_values
    P_max = P_total_values[0]
    plt.plot(r_graph_values, P_total_values / P_max, label=r"$P_{total}$", color="black")
    plt.plot(r_graph_values, P_degeneracy_values / P_max, label=r"$P_{deg}$", color="red")
    plt.plot(r_graph_values, P_gas_values / P_max, label=r"$P_{gas}$", color="green")
    plt.plot(r_graph_values, P_photon_values / P_max, label=r"$P_{photon}$", color="blue")
    mark_convective_regions()
    if reference_data is not None:
        r_ref_values = reference_data[ex_r_index, :] / surface_r
        plt.plot(r_ref_values, reference_data[ex_P_index, :] / P_max,
                 label=r"$P_{total, ref}$", color="black", linestyle='dashed')
        plt.plot(r_ref_values, reference_data[ex_P_degeneracy_index, :] / P_max,
                 label=r"$P_{deg, ref}$", color="red", linestyle='dashed')
        plt.plot(r_ref_values, reference_data[ex_P_gas_index, :] / P_max,
                 label=r"$P_{gas, ref}$", color="green", linestyle='dashed')
        plt.plot(r_ref_values, reference_data[ex_P_photon_index, :] / P_max,
                 label=r"$P_{photon, ref}$", color="blue", linestyle='dashed')
    plt.legend()
    plt.xlim(0, 1)
    plt.ylim(0, 1)
    plt.xlabel(r"$r/R_{star}$")
    plt.ylabel(r"$\frac{P}{P_c}$")
    plt.title(r"Pressure Contributions Versus Radius Fraction" + title_description)
    plt.savefig("../Graphs/" + name + "_pressure.png")
    plt.clf()

    kappa_es_values = config.kappa_es() * np.ones(len(r_graph_values))
    kappa_ff_values = config.kappa_ff(state_values[rho_index, :], state_values[T_index, :])
    kappa_H_minus_values = config.kappa_H_minus(state_values[rho_index, :], state_values[T_index, :])
    plt.plot(r_graph_values, np.log10(kappa_total_values / (cm ** 2 / g)), label=r"$\kappa_{total}$", color="black")
    plt.plot(r_graph_values, np.log10(kappa_es_values / (cm ** 2 / g)), label=r"$\kappa_{es}$", color="blue")
    plt.plot(r_graph_values, np.log10(kappa_ff_values / (cm ** 2 / g)), label=r"$\kappa_{ff}$", color="green")
    plt.plot(r_graph_values, np.log10(kappa_H_minus_values / (cm ** 2 / g)), label=r"$\kappa_{H-}$", color="red")
    mark_convective_regions()
    if reference_data is not None:
        r_ref_values = reference_data[ex_r_index, :] / surface_r
        plt.plot(r_ref_values, np.log10(reference_data[ex_kappa_index, :] / (cm ** 2 / g)),
                 label=r"$\kappa_{total, ref}$", color="black", linestyle='dashed')
        plt.plot(r_ref_values, np.log10(reference_data[ex_kappa_es_index, :] / (cm ** 2 / g)),
                 label=r"$\kappa_{es, ref}$", color="blue", linestyle='dashed')
        plt.plot(r_ref_values, np.log10(reference_data[ex_kappa_ff_index, :] / (cm ** 2 / g)),
                 label=r"$\kappa_{ff, ref}$", color="green", linestyle='dashed')
        plt.plot(r_ref_values, np.log10(reference_data[ex_kappa_H_minus_index, :] / (cm ** 2 / g)),
                 label=r"$\kappa_{H-, ref}$", color="red", linestyle='dashed')
    plt.legend()
    plt.xlim(0, 1)
    plt.xlabel(r"$r/R_{star}$")
    plt.ylabel(r"$\log_{10}(\kappa) (\frac{cm^2}{g})$")
    plt.title(r"Opacity Contributions Versus Radius Fraction" + title_description)
    plt.savefig("../Graphs/" + name + "_opacity.png")
    plt.clf()

    L_proton_proton_prime_values = config.L_proton_proton_prime(r_values,
                                                                state_values[rho_index, :],
                                                                state_values[T_index, :])
    L_CNO_prime_values = config.L_CNO_prime(r_values, state_values[rho_index, :],
                                            state_values[T_index, :])
    L_total_prime = L_proton_proton_prime_values + L_CNO_prime_values
    plt.plot(r_graph_values, L_total_prime / surface_L * surface_r, label=r"$\frac{L}{dr}$", color="black")
    plt.plot(r_graph_values, L_proton_proton_prime_values / surface_L * surface_r,
             label=r"$\frac{L_{proton-proton}}{dr}$", color="red")
    plt.plot(r_graph_values, L_CNO_prime_values / surface_L * surface_r, label=r"$\frac{L_{CNO}}{dr}$", color="blue")
    mark_convective_regions()
    if reference_data is not None:
        r_ref_values = reference_data[ex_r_index, :] / surface_r
        plt.plot(r_ref_values, reference_data[ex_L_prime_index, :] / surface_L * surface_r,
                 label=r"$\frac{L_{ref}}{dr}$", color="black", linestyle="dashed")
        plt.plot(r_ref_values, reference_data[ex_L_proton_proton_prime_index, :] / surface_L * surface_r,
                 label=r"$\frac{L_{proton-proton, ref}}{dr}$", color="red", linestyle="dashed")
        plt.plot(r_ref_values, reference_data[ex_L_CNO_prime_index, :] / surface_L * surface_r,
                 label=r"$\frac{L_{CNO, ref}}{dr}$", color="blue", linestyle="dashed")
    plt.legend()
    plt.xlim(0, 1)
    plt.title(r"Luminosity Gradient Contributions Versus Radius Fraction" + title_description)
    plt.xlabel(r"$r/R_{star}$")
    plt.ylabel(r"$\frac{dL}{dr} (\frac{L_{star}}{R_{star}})$")
    plt.savefig("../Graphs/" + name + "_luminosity.png")
    plt.clf()

    log_P_values = np.log(P_total_values)
    log_T_values = np.log(state_values[T_index, :])
    dlogP_dlogT_values = np.diff(log_P_values) / np.diff(log_T_values)  # can result in Nans
    # Omit last 2 r_graph_values since taking first difference decreases array size by 1, and last point is unstable
    plt.plot(r_graph_values[:-2], dlogP_dlogT_values[:-1], label="calculated", color="black")
    mark_convective_regions()
    if reference_data is not None:
        r_ref_values = reference_data[ex_r_index, :] / surface_r
        plt.plot(r_ref_values, reference_data[ex_dlog_P_by_dlog_T_index, :],
                 label="ref", color="black", linestyle="dashed")
    plt.xlim(0, 1)
    plt.xlabel(r"$r/R_{star}$")
    plt.ylabel(r"$\frac{dlogP}{dlogT}$")
    plt.title(r"$\frac{dlogP}{dlogT}$ Versus Radius Fraction" + title_description)
    plt.savefig("../Graphs/" + name + "_dlogP_dlogT.png")
    plt.clf()