import numpy as np
import matplotlib.pyplot as plt
import pynucastro as pyrl

Integration Example#

We can use pynucastro to generate the righthand side function for an astrophysical reaction network.

We’ll create a CNO network that has the rates for all 4 CNO cycles + hot-CNO, as listed here: https://reaclib.jinaweb.org/popularRates.php

files = ["c12-pg-n13-ls09",
         "c13-pg-n14-nacr",
         "n13--c13-wc12",
         "n13-pg-o14-lg06",
         "n14-pg-o15-im05",
         "n15-pa-c12-nacr",
         "o14--n14-wc12",
         "o15--n15-wc12",
         "n15-pg-o16-li10",
         "o16-pg-f17-ia08",
         "f17--o17-wc12",
         "o17-pa-n14-il10",
         "o17-pg-f18-il10",
         "f18--o18-wc12",
         "o18-pa-n15-il10",
         "o18-pg-f19-il10",
         "f19-pa-o16-nacr",
         "o14-ap-f17-Ha96c",
         "f17-pg-ne18-cb09",
         "ne18--f18-wc12",
         "f18-pa-o15-il10"]
rc = pyrl.RateCollection(files)

We can visualize the network and rates linking the nuclei

rc.plot()
../_images/integration-example_5_0.png ../_images/integration-example_5_1.png

pynucastro can write out the python code needed to evaluate the reaction rates

pynet = pyrl.PythonNetwork(files)
pynet.write_network("cno_integration_example.py")
%cat cno_integration_example.py
import numba
import numpy as np
from numba.experimental import jitclass

from pynucastro.rates import Tfactors, _find_rate_file
from pynucastro.screening import PlasmaState, ScreenFactors

jp = 0
jhe4 = 1
jc12 = 2
jc13 = 3
jn13 = 4
jn14 = 5
jn15 = 6
jo14 = 7
jo15 = 8
jo16 = 9
jo17 = 10
jo18 = 11
jf17 = 12
jf18 = 13
jf19 = 14
jne18 = 15
nnuc = 16

A = np.zeros((nnuc), dtype=np.int32)

A[jp] = 1
A[jhe4] = 4
A[jc12] = 12
A[jc13] = 13
A[jn13] = 13
A[jn14] = 14
A[jn15] = 15
A[jo14] = 14
A[jo15] = 15
A[jo16] = 16
A[jo17] = 17
A[jo18] = 18
A[jf17] = 17
A[jf18] = 18
A[jf19] = 19
A[jne18] = 18

Z = np.zeros((nnuc), dtype=np.int32)

Z[jp] = 1
Z[jhe4] = 2
Z[jc12] = 6
Z[jc13] = 6
Z[jn13] = 7
Z[jn14] = 7
Z[jn15] = 7
Z[jo14] = 8
Z[jo15] = 8
Z[jo16] = 8
Z[jo17] = 8
Z[jo18] = 8
Z[jf17] = 9
Z[jf18] = 9
Z[jf19] = 9
Z[jne18] = 10

names = []
names.append("h1")
names.append("he4")
names.append("c12")
names.append("c13")
names.append("n13")
names.append("n14")
names.append("n15")
names.append("o14")
names.append("o15")
names.append("o16")
names.append("o17")
names.append("o18")
names.append("f17")
names.append("f18")
names.append("f19")
names.append("ne18")

@jitclass([
    ("p_c12__n13", numba.float64),
    ("p_c13__n14", numba.float64),
    ("n13__c13__weak__wc12", numba.float64),
    ("p_n13__o14", numba.float64),
    ("p_n14__o15", numba.float64),
    ("p_n15__he4_c12", numba.float64),
    ("o14__n14__weak__wc12", numba.float64),
    ("o15__n15__weak__wc12", numba.float64),
    ("p_n15__o16", numba.float64),
    ("p_o16__f17", numba.float64),
    ("f17__o17__weak__wc12", numba.float64),
    ("p_o17__he4_n14", numba.float64),
    ("p_o17__f18", numba.float64),
    ("f18__o18__weak__wc12", numba.float64),
    ("p_o18__he4_n15", numba.float64),
    ("p_o18__f19", numba.float64),
    ("p_f19__he4_o16", numba.float64),
    ("he4_o14__p_f17", numba.float64),
    ("p_f17__ne18", numba.float64),
    ("ne18__f18__weak__wc12", numba.float64),
    ("p_f18__he4_o15", numba.float64),
])
class RateEval:
    def __init__(self):
        self.p_c12__n13 = np.nan
        self.p_c13__n14 = np.nan
        self.n13__c13__weak__wc12 = np.nan
        self.p_n13__o14 = np.nan
        self.p_n14__o15 = np.nan
        self.p_n15__he4_c12 = np.nan
        self.o14__n14__weak__wc12 = np.nan
        self.o15__n15__weak__wc12 = np.nan
        self.p_n15__o16 = np.nan
        self.p_o16__f17 = np.nan
        self.f17__o17__weak__wc12 = np.nan
        self.p_o17__he4_n14 = np.nan
        self.p_o17__f18 = np.nan
        self.f18__o18__weak__wc12 = np.nan
        self.p_o18__he4_n15 = np.nan
        self.p_o18__f19 = np.nan
        self.p_f19__he4_o16 = np.nan
        self.he4_o14__p_f17 = np.nan
        self.p_f17__ne18 = np.nan
        self.ne18__f18__weak__wc12 = np.nan
        self.p_f18__he4_o15 = np.nan

@numba.njit()
def ye(Y):
    return np.sum(Z * Y)/np.sum(A * Y)

@numba.njit()
def p_c12__n13(rate_eval, tf):
    # c12 + p --> n13
    rate = 0.0

    # ls09n
    rate += np.exp(  17.1482 + -13.692*tf.T913i + -0.230881*tf.T913
                  + 4.44362*tf.T9 + -3.15898*tf.T953 + -0.666667*tf.lnT9)
    # ls09r
    rate += np.exp(  17.5428 + -3.77849*tf.T9i + -5.10735*tf.T913i + -2.24111*tf.T913
                  + 0.148883*tf.T9 + -1.5*tf.lnT9)

    rate_eval.p_c12__n13 = rate

@numba.njit()
def p_c13__n14(rate_eval, tf):
    # c13 + p --> n14
    rate = 0.0

    # nacrn
    rate += np.exp(  18.5155 + -13.72*tf.T913i + -0.450018*tf.T913
                  + 3.70823*tf.T9 + -1.70545*tf.T953 + -0.666667*tf.lnT9)
    # nacrr
    rate += np.exp(  13.9637 + -5.78147*tf.T9i + -0.196703*tf.T913
                  + 0.142126*tf.T9 + -0.0238912*tf.T953 + -1.5*tf.lnT9)
    # nacrr
    rate += np.exp(  15.1825 + -13.5543*tf.T9i
                  + -1.5*tf.lnT9)

    rate_eval.p_c13__n14 = rate

@numba.njit()
def n13__c13__weak__wc12(rate_eval, tf):
    # n13 --> c13
    rate = 0.0

    # wc12w
    rate += np.exp(  -6.7601)

    rate_eval.n13__c13__weak__wc12 = rate

@numba.njit()
def p_n13__o14(rate_eval, tf):
    # n13 + p --> o14
    rate = 0.0

    # lg06n
    rate += np.exp(  18.1356 + -15.1676*tf.T913i + 0.0955166*tf.T913
                  + 3.0659*tf.T9 + -0.507339*tf.T953 + -0.666667*tf.lnT9)
    # lg06r
    rate += np.exp(  10.9971 + -6.12602*tf.T9i + 1.57122*tf.T913i
                  + -1.5*tf.lnT9)

    rate_eval.p_n13__o14 = rate

@numba.njit()
def p_n14__o15(rate_eval, tf):
    # n14 + p --> o15
    rate = 0.0

    # im05n
    rate += np.exp(  17.01 + -15.193*tf.T913i + -0.161954*tf.T913
                  + -7.52123*tf.T9 + -0.987565*tf.T953 + -0.666667*tf.lnT9)
    # im05r
    rate += np.exp(  6.73578 + -4.891*tf.T9i
                  + 0.0682*tf.lnT9)
    # im05r
    rate += np.exp(  7.65444 + -2.998*tf.T9i
                  + -1.5*tf.lnT9)
    # im05n
    rate += np.exp(  20.1169 + -15.193*tf.T913i + -4.63975*tf.T913
                  + 9.73458*tf.T9 + -9.55051*tf.T953 + 0.333333*tf.lnT9)

    rate_eval.p_n14__o15 = rate

@numba.njit()
def p_n15__he4_c12(rate_eval, tf):
    # n15 + p --> he4 + c12
    rate = 0.0

    # nacrn
    rate += np.exp(  27.4764 + -15.253*tf.T913i + 1.59318*tf.T913
                  + 2.4479*tf.T9 + -2.19708*tf.T953 + -0.666667*tf.lnT9)
    # nacrr
    rate += np.exp(  -6.57522 + -1.1638*tf.T9i + 22.7105*tf.T913
                  + -2.90707*tf.T9 + 0.205754*tf.T953 + -1.5*tf.lnT9)
    # nacrr
    rate += np.exp(  20.8972 + -7.406*tf.T9i
                  + -1.5*tf.lnT9)
    # nacrr
    rate += np.exp(  -4.87347 + -2.02117*tf.T9i + 30.8497*tf.T913
                  + -8.50433*tf.T9 + -1.54426*tf.T953 + -1.5*tf.lnT9)

    rate_eval.p_n15__he4_c12 = rate

@numba.njit()
def o14__n14__weak__wc12(rate_eval, tf):
    # o14 --> n14
    rate = 0.0

    # wc12w
    rate += np.exp(  -4.62354)

    rate_eval.o14__n14__weak__wc12 = rate

@numba.njit()
def o15__n15__weak__wc12(rate_eval, tf):
    # o15 --> n15
    rate = 0.0

    # wc12w
    rate += np.exp(  -5.17053)

    rate_eval.o15__n15__weak__wc12 = rate

@numba.njit()
def p_n15__o16(rate_eval, tf):
    # n15 + p --> o16
    rate = 0.0

    # li10r
    rate += np.exp(  14.5444 + -10.2295*tf.T9i
                  + 0.0459037*tf.T9 + -1.5*tf.lnT9)
    # li10r
    rate += np.exp(  6.59056 + -2.92315*tf.T9i
                  + -1.5*tf.lnT9)
    # li10n
    rate += np.exp(  20.0176 + -15.24*tf.T913i + 0.334926*tf.T913
                  + 4.59088*tf.T9 + -4.78468*tf.T953 + -0.666667*tf.lnT9)

    rate_eval.p_n15__o16 = rate

@numba.njit()
def p_o16__f17(rate_eval, tf):
    # o16 + p --> f17
    rate = 0.0

    # ia08n
    rate += np.exp(  19.0904 + -16.696*tf.T913i + -1.16252*tf.T913
                  + 0.267703*tf.T9 + -0.0338411*tf.T953 + -0.666667*tf.lnT9)

    rate_eval.p_o16__f17 = rate

@numba.njit()
def f17__o17__weak__wc12(rate_eval, tf):
    # f17 --> o17
    rate = 0.0

    # wc12w
    rate += np.exp(  -4.53318)

    rate_eval.f17__o17__weak__wc12 = rate

@numba.njit()
def p_o17__he4_n14(rate_eval, tf):
    # o17 + p --> he4 + n14
    rate = 0.0

    # il10r
    rate += np.exp(  10.174 + -4.95865*tf.T9i + 5.10182*tf.T913
                  + 0.379373*tf.T9 + -0.0672515*tf.T953 + -1.5*tf.lnT9)
    # il10r
    rate += np.exp(  5.5336 + -2.11477*tf.T9i
                  + -1.5*tf.lnT9)
    # il10r
    rate += np.exp(  -7.20763 + -0.753395*tf.T9i
                  + -1.5*tf.lnT9)
    # il10n
    rate += np.exp(  19.579 + -16.9078*tf.T913i
                  + -2.0*tf.T953 + -0.666667*tf.lnT9)

    rate_eval.p_o17__he4_n14 = rate

@numba.njit()
def p_o17__f18(rate_eval, tf):
    # o17 + p --> f18
    rate = 0.0

    # il10r
    rate += np.exp(  9.39048 + -6.22828*tf.T9i + 2.31435*tf.T913
                  + -0.302835*tf.T9 + 0.020133*tf.T953 + -1.5*tf.lnT9)
    # il10r
    rate += np.exp(  -13.077 + -0.746296*tf.T9i
                  + -1.5*tf.lnT9)
    # il10n
    rate += np.exp(  15.8929 + -16.4035*tf.T913i + 4.31885*tf.T913
                  + -0.709921*tf.T9 + -2.0*tf.T953 + -0.666667*tf.lnT9)

    rate_eval.p_o17__f18 = rate

@numba.njit()
def f18__o18__weak__wc12(rate_eval, tf):
    # f18 --> o18
    rate = 0.0

    # wc12w
    rate += np.exp(  -9.15982)

    rate_eval.f18__o18__weak__wc12 = rate

@numba.njit()
def p_o18__he4_n15(rate_eval, tf):
    # o18 + p --> he4 + n15
    rate = 0.0

    # il10r
    rate += np.exp(  -27.9044 + -0.245884*tf.T9i
                  + -1.5*tf.lnT9)
    # il10n
    rate += np.exp(  26.9671 + -16.6979*tf.T913i
                  + -3.0*tf.T953 + -0.666667*tf.lnT9)
    # il10r
    rate += np.exp(  8.94352 + -5.32335*tf.T9i + 11.6568*tf.T913
                  + -2.16303*tf.T9 + 0.209965*tf.T953 + -1.5*tf.lnT9)
    # il10r
    rate += np.exp(  10.2725 + -1.663*tf.T9i
                  + -1.5*tf.lnT9)

    rate_eval.p_o18__he4_n15 = rate

@numba.njit()
def p_o18__f19(rate_eval, tf):
    # o18 + p --> f19
    rate = 0.0

    # il10r
    rate += np.exp(  5.07648 + -1.65681*tf.T9i
                  + -1.5*tf.lnT9)
    # il10r
    rate += np.exp(  -35.0079 + -0.244743*tf.T9i
                  + -1.5*tf.lnT9)
    # il10n
    rate += np.exp(  19.917 + -16.7246*tf.T913i
                  + -3.0*tf.T953 + -0.666667*tf.lnT9)
    # il10r
    rate += np.exp(  7.26876 + -6.7253*tf.T9i + 3.99059*tf.T913
                  + -0.593127*tf.T9 + 0.0877534*tf.T953 + -1.5*tf.lnT9)

    rate_eval.p_o18__f19 = rate

@numba.njit()
def p_f19__he4_o16(rate_eval, tf):
    # f19 + p --> he4 + o16
    rate = 0.0

    # nacr 
    rate += np.exp(  15.1955 + -3.75185*tf.T9i
                  + -1.5*tf.lnT9)
    # nacr 
    rate += np.exp(  8.239 + -2.46828*tf.T9i
                  + -1.5*tf.lnT9)
    # nacr 
    rate += np.exp(  -52.7043 + -0.12765*tf.T9i
                  + -1.5*tf.lnT9)
    # nacr 
    rate += np.exp(  26.2916 + -18.116*tf.T913i
                  + 1.86674*tf.T9 + -7.5666*tf.T953 + -0.666667*tf.lnT9)
    # nacrr
    rate += np.exp(  14.3586 + -3.286*tf.T9i
                  + -0.21103*tf.T9 + 2.87702*tf.lnT9)

    rate_eval.p_f19__he4_o16 = rate

@numba.njit()
def he4_o14__p_f17(rate_eval, tf):
    # o14 + he4 --> p + f17
    rate = 0.0

    # Ha96n
    rate += np.exp(  40.8358 + -39.388*tf.T913i + -17.4673*tf.T913
                  + 35.3029*tf.T9 + -24.8162*tf.T953 + -0.666667*tf.lnT9)
    # Ha96r
    rate += np.exp(  16.3087 + -22.51*tf.T9i
                  + -1.5*tf.lnT9)
    # Ha96r
    rate += np.exp(  11.1184 + -13.6*tf.T9i
                  + -1.5*tf.lnT9)
    # Ha96r
    rate += np.exp(  -106.091 + -0.453036*tf.T9i
                  + -1.5*tf.lnT9)
    # Ha96r
    rate += np.exp(  12.1289 + -12.0223*tf.T9i
                  + -1.5*tf.lnT9)
    # Ha96r
    rate += np.exp(  18.6518 + -26.0*tf.T9i
                  + -1.5*tf.lnT9)

    rate_eval.he4_o14__p_f17 = rate

@numba.njit()
def p_f17__ne18(rate_eval, tf):
    # f17 + p --> ne18
    rate = 0.0

    # cb09 
    rate += np.exp(  27.5778 + -4.95969*tf.T9i + -21.3249*tf.T913i + -0.230774*tf.T913
                  + 0.917931*tf.T9 + -0.0440377*tf.T953 + -7.36014*tf.lnT9)
    # cb09 
    rate += np.exp(  -7.84708 + -0.0323504*tf.T9i + -14.2191*tf.T913i + 34.0647*tf.T913
                  + -16.5698*tf.T9 + 2.48116*tf.T953 + -2.13376*tf.lnT9)

    rate_eval.p_f17__ne18 = rate

@numba.njit()
def ne18__f18__weak__wc12(rate_eval, tf):
    # ne18 --> f18
    rate = 0.0

    # wc12w
    rate += np.exp(  -0.879336)

    rate_eval.ne18__f18__weak__wc12 = rate

@numba.njit()
def p_f18__he4_o15(rate_eval, tf):
    # f18 + p --> he4 + o15
    rate = 0.0

    # il10n
    rate += np.exp(  62.0058 + -21.4023*tf.T913i + -80.8891*tf.T913
                  + 134.6*tf.T9 + -126.504*tf.T953 + -0.666667*tf.lnT9)
    # il10r
    rate += np.exp(  1.75704 + -3.01675*tf.T9i + 13.3223*tf.T913
                  + -1.36696*tf.T9 + 0.0757363*tf.T953 + -1.5*tf.lnT9)
    # il10r
    rate += np.exp(  -31.7388 + -0.376432*tf.T9i + 61.738*tf.T913
                  + -108.29*tf.T9 + -34.2365*tf.T953 + -1.5*tf.lnT9)

    rate_eval.p_f18__he4_o15 = rate

def rhs(t, Y, rho, T, screen_func=None):
    return rhs_eq(t, Y, rho, T, screen_func)

@numba.njit()
def rhs_eq(t, Y, rho, T, screen_func):

    tf = Tfactors(T)
    rate_eval = RateEval()

    # reaclib rates
    p_c12__n13(rate_eval, tf)
    p_c13__n14(rate_eval, tf)
    n13__c13__weak__wc12(rate_eval, tf)
    p_n13__o14(rate_eval, tf)
    p_n14__o15(rate_eval, tf)
    p_n15__he4_c12(rate_eval, tf)
    o14__n14__weak__wc12(rate_eval, tf)
    o15__n15__weak__wc12(rate_eval, tf)
    p_n15__o16(rate_eval, tf)
    p_o16__f17(rate_eval, tf)
    f17__o17__weak__wc12(rate_eval, tf)
    p_o17__he4_n14(rate_eval, tf)
    p_o17__f18(rate_eval, tf)
    f18__o18__weak__wc12(rate_eval, tf)
    p_o18__he4_n15(rate_eval, tf)
    p_o18__f19(rate_eval, tf)
    p_f19__he4_o16(rate_eval, tf)
    he4_o14__p_f17(rate_eval, tf)
    p_f17__ne18(rate_eval, tf)
    ne18__f18__weak__wc12(rate_eval, tf)
    p_f18__he4_o15(rate_eval, tf)

    if screen_func is not None:
        plasma_state = PlasmaState(T, rho, Y, Z)

        scn_fac = ScreenFactors(1, 1, 6, 12)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_c12__n13 *= scor

        scn_fac = ScreenFactors(1, 1, 6, 13)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_c13__n14 *= scor

        scn_fac = ScreenFactors(1, 1, 7, 13)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_n13__o14 *= scor

        scn_fac = ScreenFactors(1, 1, 7, 14)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_n14__o15 *= scor

        scn_fac = ScreenFactors(1, 1, 7, 15)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_n15__he4_c12 *= scor
        rate_eval.p_n15__o16 *= scor

        scn_fac = ScreenFactors(1, 1, 8, 16)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_o16__f17 *= scor

        scn_fac = ScreenFactors(1, 1, 8, 17)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_o17__he4_n14 *= scor
        rate_eval.p_o17__f18 *= scor

        scn_fac = ScreenFactors(1, 1, 8, 18)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_o18__he4_n15 *= scor
        rate_eval.p_o18__f19 *= scor

        scn_fac = ScreenFactors(1, 1, 9, 19)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_f19__he4_o16 *= scor

        scn_fac = ScreenFactors(2, 4, 8, 14)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.he4_o14__p_f17 *= scor

        scn_fac = ScreenFactors(1, 1, 9, 17)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_f17__ne18 *= scor

        scn_fac = ScreenFactors(1, 1, 9, 18)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_f18__he4_o15 *= scor

    dYdt = np.zeros((nnuc), dtype=np.float64)

    dYdt[jp] = (
       -rho*Y[jp]*Y[jc12]*rate_eval.p_c12__n13
       -rho*Y[jp]*Y[jc13]*rate_eval.p_c13__n14
       -rho*Y[jp]*Y[jn13]*rate_eval.p_n13__o14
       -rho*Y[jp]*Y[jn14]*rate_eval.p_n14__o15
       -rho*Y[jp]*Y[jn15]*rate_eval.p_n15__he4_c12
       -rho*Y[jp]*Y[jn15]*rate_eval.p_n15__o16
       -rho*Y[jp]*Y[jo16]*rate_eval.p_o16__f17
       -rho*Y[jp]*Y[jo17]*rate_eval.p_o17__he4_n14
       -rho*Y[jp]*Y[jo17]*rate_eval.p_o17__f18
       -rho*Y[jp]*Y[jo18]*rate_eval.p_o18__he4_n15
       -rho*Y[jp]*Y[jo18]*rate_eval.p_o18__f19
       -rho*Y[jp]*Y[jf19]*rate_eval.p_f19__he4_o16
       -rho*Y[jp]*Y[jf17]*rate_eval.p_f17__ne18
       -rho*Y[jp]*Y[jf18]*rate_eval.p_f18__he4_o15
       +rho*Y[jhe4]*Y[jo14]*rate_eval.he4_o14__p_f17
       )

    dYdt[jhe4] = (
       -rho*Y[jhe4]*Y[jo14]*rate_eval.he4_o14__p_f17
       +rho*Y[jp]*Y[jn15]*rate_eval.p_n15__he4_c12
       +rho*Y[jp]*Y[jo17]*rate_eval.p_o17__he4_n14
       +rho*Y[jp]*Y[jo18]*rate_eval.p_o18__he4_n15
       +rho*Y[jp]*Y[jf19]*rate_eval.p_f19__he4_o16
       +rho*Y[jp]*Y[jf18]*rate_eval.p_f18__he4_o15
       )

    dYdt[jc12] = (
       -rho*Y[jp]*Y[jc12]*rate_eval.p_c12__n13
       +rho*Y[jp]*Y[jn15]*rate_eval.p_n15__he4_c12
       )

    dYdt[jc13] = (
       -rho*Y[jp]*Y[jc13]*rate_eval.p_c13__n14
       +Y[jn13]*rate_eval.n13__c13__weak__wc12
       )

    dYdt[jn13] = (
       -Y[jn13]*rate_eval.n13__c13__weak__wc12
       -rho*Y[jp]*Y[jn13]*rate_eval.p_n13__o14
       +rho*Y[jp]*Y[jc12]*rate_eval.p_c12__n13
       )

    dYdt[jn14] = (
       -rho*Y[jp]*Y[jn14]*rate_eval.p_n14__o15
       +rho*Y[jp]*Y[jc13]*rate_eval.p_c13__n14
       +Y[jo14]*rate_eval.o14__n14__weak__wc12
       +rho*Y[jp]*Y[jo17]*rate_eval.p_o17__he4_n14
       )

    dYdt[jn15] = (
       -rho*Y[jp]*Y[jn15]*rate_eval.p_n15__he4_c12
       -rho*Y[jp]*Y[jn15]*rate_eval.p_n15__o16
       +Y[jo15]*rate_eval.o15__n15__weak__wc12
       +rho*Y[jp]*Y[jo18]*rate_eval.p_o18__he4_n15
       )

    dYdt[jo14] = (
       -Y[jo14]*rate_eval.o14__n14__weak__wc12
       -rho*Y[jhe4]*Y[jo14]*rate_eval.he4_o14__p_f17
       +rho*Y[jp]*Y[jn13]*rate_eval.p_n13__o14
       )

    dYdt[jo15] = (
       -Y[jo15]*rate_eval.o15__n15__weak__wc12
       +rho*Y[jp]*Y[jn14]*rate_eval.p_n14__o15
       +rho*Y[jp]*Y[jf18]*rate_eval.p_f18__he4_o15
       )

    dYdt[jo16] = (
       -rho*Y[jp]*Y[jo16]*rate_eval.p_o16__f17
       +rho*Y[jp]*Y[jn15]*rate_eval.p_n15__o16
       +rho*Y[jp]*Y[jf19]*rate_eval.p_f19__he4_o16
       )

    dYdt[jo17] = (
       -rho*Y[jp]*Y[jo17]*rate_eval.p_o17__he4_n14
       -rho*Y[jp]*Y[jo17]*rate_eval.p_o17__f18
       +Y[jf17]*rate_eval.f17__o17__weak__wc12
       )

    dYdt[jo18] = (
       -rho*Y[jp]*Y[jo18]*rate_eval.p_o18__he4_n15
       -rho*Y[jp]*Y[jo18]*rate_eval.p_o18__f19
       +Y[jf18]*rate_eval.f18__o18__weak__wc12
       )

    dYdt[jf17] = (
       -Y[jf17]*rate_eval.f17__o17__weak__wc12
       -rho*Y[jp]*Y[jf17]*rate_eval.p_f17__ne18
       +rho*Y[jp]*Y[jo16]*rate_eval.p_o16__f17
       +rho*Y[jhe4]*Y[jo14]*rate_eval.he4_o14__p_f17
       )

    dYdt[jf18] = (
       -Y[jf18]*rate_eval.f18__o18__weak__wc12
       -rho*Y[jp]*Y[jf18]*rate_eval.p_f18__he4_o15
       +rho*Y[jp]*Y[jo17]*rate_eval.p_o17__f18
       +Y[jne18]*rate_eval.ne18__f18__weak__wc12
       )

    dYdt[jf19] = (
       -rho*Y[jp]*Y[jf19]*rate_eval.p_f19__he4_o16
       +rho*Y[jp]*Y[jo18]*rate_eval.p_o18__f19
       )

    dYdt[jne18] = (
       -Y[jne18]*rate_eval.ne18__f18__weak__wc12
       +rho*Y[jp]*Y[jf17]*rate_eval.p_f17__ne18
       )

    return dYdt

def jacobian(t, Y, rho, T, screen_func=None):
    return jacobian_eq(t, Y, rho, T, screen_func)

@numba.njit()
def jacobian_eq(t, Y, rho, T, screen_func):

    tf = Tfactors(T)
    rate_eval = RateEval()

    # reaclib rates
    p_c12__n13(rate_eval, tf)
    p_c13__n14(rate_eval, tf)
    n13__c13__weak__wc12(rate_eval, tf)
    p_n13__o14(rate_eval, tf)
    p_n14__o15(rate_eval, tf)
    p_n15__he4_c12(rate_eval, tf)
    o14__n14__weak__wc12(rate_eval, tf)
    o15__n15__weak__wc12(rate_eval, tf)
    p_n15__o16(rate_eval, tf)
    p_o16__f17(rate_eval, tf)
    f17__o17__weak__wc12(rate_eval, tf)
    p_o17__he4_n14(rate_eval, tf)
    p_o17__f18(rate_eval, tf)
    f18__o18__weak__wc12(rate_eval, tf)
    p_o18__he4_n15(rate_eval, tf)
    p_o18__f19(rate_eval, tf)
    p_f19__he4_o16(rate_eval, tf)
    he4_o14__p_f17(rate_eval, tf)
    p_f17__ne18(rate_eval, tf)
    ne18__f18__weak__wc12(rate_eval, tf)
    p_f18__he4_o15(rate_eval, tf)

    if screen_func is not None:
        plasma_state = PlasmaState(T, rho, Y, Z)

        scn_fac = ScreenFactors(1, 1, 6, 12)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_c12__n13 *= scor

        scn_fac = ScreenFactors(1, 1, 6, 13)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_c13__n14 *= scor

        scn_fac = ScreenFactors(1, 1, 7, 13)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_n13__o14 *= scor

        scn_fac = ScreenFactors(1, 1, 7, 14)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_n14__o15 *= scor

        scn_fac = ScreenFactors(1, 1, 7, 15)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_n15__he4_c12 *= scor
        rate_eval.p_n15__o16 *= scor

        scn_fac = ScreenFactors(1, 1, 8, 16)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_o16__f17 *= scor

        scn_fac = ScreenFactors(1, 1, 8, 17)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_o17__he4_n14 *= scor
        rate_eval.p_o17__f18 *= scor

        scn_fac = ScreenFactors(1, 1, 8, 18)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_o18__he4_n15 *= scor
        rate_eval.p_o18__f19 *= scor

        scn_fac = ScreenFactors(1, 1, 9, 19)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_f19__he4_o16 *= scor

        scn_fac = ScreenFactors(2, 4, 8, 14)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.he4_o14__p_f17 *= scor

        scn_fac = ScreenFactors(1, 1, 9, 17)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_f17__ne18 *= scor

        scn_fac = ScreenFactors(1, 1, 9, 18)
        scor = screen_func(plasma_state, scn_fac)
        rate_eval.p_f18__he4_o15 *= scor

    jac = np.zeros((nnuc, nnuc), dtype=np.float64)

    jac[jp, jp] = (
       -rho*Y[jc12]*rate_eval.p_c12__n13
       -rho*Y[jc13]*rate_eval.p_c13__n14
       -rho*Y[jn13]*rate_eval.p_n13__o14
       -rho*Y[jn14]*rate_eval.p_n14__o15
       -rho*Y[jn15]*rate_eval.p_n15__he4_c12
       -rho*Y[jn15]*rate_eval.p_n15__o16
       -rho*Y[jo16]*rate_eval.p_o16__f17
       -rho*Y[jo17]*rate_eval.p_o17__he4_n14
       -rho*Y[jo17]*rate_eval.p_o17__f18
       -rho*Y[jo18]*rate_eval.p_o18__he4_n15
       -rho*Y[jo18]*rate_eval.p_o18__f19
       -rho*Y[jf19]*rate_eval.p_f19__he4_o16
       -rho*Y[jf17]*rate_eval.p_f17__ne18
       -rho*Y[jf18]*rate_eval.p_f18__he4_o15
       )

    jac[jp, jhe4] = (
       +rho*Y[jo14]*rate_eval.he4_o14__p_f17
       )

    jac[jp, jc12] = (
       -rho*Y[jp]*rate_eval.p_c12__n13
       )

    jac[jp, jc13] = (
       -rho*Y[jp]*rate_eval.p_c13__n14
       )

    jac[jp, jn13] = (
       -rho*Y[jp]*rate_eval.p_n13__o14
       )

    jac[jp, jn14] = (
       -rho*Y[jp]*rate_eval.p_n14__o15
       )

    jac[jp, jn15] = (
       -rho*Y[jp]*rate_eval.p_n15__he4_c12
       -rho*Y[jp]*rate_eval.p_n15__o16
       )

    jac[jp, jo14] = (
       +rho*Y[jhe4]*rate_eval.he4_o14__p_f17
       )

    jac[jp, jo16] = (
       -rho*Y[jp]*rate_eval.p_o16__f17
       )

    jac[jp, jo17] = (
       -rho*Y[jp]*rate_eval.p_o17__he4_n14
       -rho*Y[jp]*rate_eval.p_o17__f18
       )

    jac[jp, jo18] = (
       -rho*Y[jp]*rate_eval.p_o18__he4_n15
       -rho*Y[jp]*rate_eval.p_o18__f19
       )

    jac[jp, jf17] = (
       -rho*Y[jp]*rate_eval.p_f17__ne18
       )

    jac[jp, jf18] = (
       -rho*Y[jp]*rate_eval.p_f18__he4_o15
       )

    jac[jp, jf19] = (
       -rho*Y[jp]*rate_eval.p_f19__he4_o16
       )

    jac[jhe4, jp] = (
       +rho*Y[jn15]*rate_eval.p_n15__he4_c12
       +rho*Y[jo17]*rate_eval.p_o17__he4_n14
       +rho*Y[jo18]*rate_eval.p_o18__he4_n15
       +rho*Y[jf19]*rate_eval.p_f19__he4_o16
       +rho*Y[jf18]*rate_eval.p_f18__he4_o15
       )

    jac[jhe4, jhe4] = (
       -rho*Y[jo14]*rate_eval.he4_o14__p_f17
       )

    jac[jhe4, jn15] = (
       +rho*Y[jp]*rate_eval.p_n15__he4_c12
       )

    jac[jhe4, jo14] = (
       -rho*Y[jhe4]*rate_eval.he4_o14__p_f17
       )

    jac[jhe4, jo17] = (
       +rho*Y[jp]*rate_eval.p_o17__he4_n14
       )

    jac[jhe4, jo18] = (
       +rho*Y[jp]*rate_eval.p_o18__he4_n15
       )

    jac[jhe4, jf18] = (
       +rho*Y[jp]*rate_eval.p_f18__he4_o15
       )

    jac[jhe4, jf19] = (
       +rho*Y[jp]*rate_eval.p_f19__he4_o16
       )

    jac[jc12, jp] = (
       -rho*Y[jc12]*rate_eval.p_c12__n13
       +rho*Y[jn15]*rate_eval.p_n15__he4_c12
       )

    jac[jc12, jc12] = (
       -rho*Y[jp]*rate_eval.p_c12__n13
       )

    jac[jc12, jn15] = (
       +rho*Y[jp]*rate_eval.p_n15__he4_c12
       )

    jac[jc13, jp] = (
       -rho*Y[jc13]*rate_eval.p_c13__n14
       )

    jac[jc13, jc13] = (
       -rho*Y[jp]*rate_eval.p_c13__n14
       )

    jac[jc13, jn13] = (
       +rate_eval.n13__c13__weak__wc12
       )

    jac[jn13, jp] = (
       -rho*Y[jn13]*rate_eval.p_n13__o14
       +rho*Y[jc12]*rate_eval.p_c12__n13
       )

    jac[jn13, jc12] = (
       +rho*Y[jp]*rate_eval.p_c12__n13
       )

    jac[jn13, jn13] = (
       -rate_eval.n13__c13__weak__wc12
       -rho*Y[jp]*rate_eval.p_n13__o14
       )

    jac[jn14, jp] = (
       -rho*Y[jn14]*rate_eval.p_n14__o15
       +rho*Y[jc13]*rate_eval.p_c13__n14
       +rho*Y[jo17]*rate_eval.p_o17__he4_n14
       )

    jac[jn14, jc13] = (
       +rho*Y[jp]*rate_eval.p_c13__n14
       )

    jac[jn14, jn14] = (
       -rho*Y[jp]*rate_eval.p_n14__o15
       )

    jac[jn14, jo14] = (
       +rate_eval.o14__n14__weak__wc12
       )

    jac[jn14, jo17] = (
       +rho*Y[jp]*rate_eval.p_o17__he4_n14
       )

    jac[jn15, jp] = (
       -rho*Y[jn15]*rate_eval.p_n15__he4_c12
       -rho*Y[jn15]*rate_eval.p_n15__o16
       +rho*Y[jo18]*rate_eval.p_o18__he4_n15
       )

    jac[jn15, jn15] = (
       -rho*Y[jp]*rate_eval.p_n15__he4_c12
       -rho*Y[jp]*rate_eval.p_n15__o16
       )

    jac[jn15, jo15] = (
       +rate_eval.o15__n15__weak__wc12
       )

    jac[jn15, jo18] = (
       +rho*Y[jp]*rate_eval.p_o18__he4_n15
       )

    jac[jo14, jp] = (
       +rho*Y[jn13]*rate_eval.p_n13__o14
       )

    jac[jo14, jhe4] = (
       -rho*Y[jo14]*rate_eval.he4_o14__p_f17
       )

    jac[jo14, jn13] = (
       +rho*Y[jp]*rate_eval.p_n13__o14
       )

    jac[jo14, jo14] = (
       -rate_eval.o14__n14__weak__wc12
       -rho*Y[jhe4]*rate_eval.he4_o14__p_f17
       )

    jac[jo15, jp] = (
       +rho*Y[jn14]*rate_eval.p_n14__o15
       +rho*Y[jf18]*rate_eval.p_f18__he4_o15
       )

    jac[jo15, jn14] = (
       +rho*Y[jp]*rate_eval.p_n14__o15
       )

    jac[jo15, jo15] = (
       -rate_eval.o15__n15__weak__wc12
       )

    jac[jo15, jf18] = (
       +rho*Y[jp]*rate_eval.p_f18__he4_o15
       )

    jac[jo16, jp] = (
       -rho*Y[jo16]*rate_eval.p_o16__f17
       +rho*Y[jn15]*rate_eval.p_n15__o16
       +rho*Y[jf19]*rate_eval.p_f19__he4_o16
       )

    jac[jo16, jn15] = (
       +rho*Y[jp]*rate_eval.p_n15__o16
       )

    jac[jo16, jo16] = (
       -rho*Y[jp]*rate_eval.p_o16__f17
       )

    jac[jo16, jf19] = (
       +rho*Y[jp]*rate_eval.p_f19__he4_o16
       )

    jac[jo17, jp] = (
       -rho*Y[jo17]*rate_eval.p_o17__he4_n14
       -rho*Y[jo17]*rate_eval.p_o17__f18
       )

    jac[jo17, jo17] = (
       -rho*Y[jp]*rate_eval.p_o17__he4_n14
       -rho*Y[jp]*rate_eval.p_o17__f18
       )

    jac[jo17, jf17] = (
       +rate_eval.f17__o17__weak__wc12
       )

    jac[jo18, jp] = (
       -rho*Y[jo18]*rate_eval.p_o18__he4_n15
       -rho*Y[jo18]*rate_eval.p_o18__f19
       )

    jac[jo18, jo18] = (
       -rho*Y[jp]*rate_eval.p_o18__he4_n15
       -rho*Y[jp]*rate_eval.p_o18__f19
       )

    jac[jo18, jf18] = (
       +rate_eval.f18__o18__weak__wc12
       )

    jac[jf17, jp] = (
       -rho*Y[jf17]*rate_eval.p_f17__ne18
       +rho*Y[jo16]*rate_eval.p_o16__f17
       )

    jac[jf17, jhe4] = (
       +rho*Y[jo14]*rate_eval.he4_o14__p_f17
       )

    jac[jf17, jo14] = (
       +rho*Y[jhe4]*rate_eval.he4_o14__p_f17
       )

    jac[jf17, jo16] = (
       +rho*Y[jp]*rate_eval.p_o16__f17
       )

    jac[jf17, jf17] = (
       -rate_eval.f17__o17__weak__wc12
       -rho*Y[jp]*rate_eval.p_f17__ne18
       )

    jac[jf18, jp] = (
       -rho*Y[jf18]*rate_eval.p_f18__he4_o15
       +rho*Y[jo17]*rate_eval.p_o17__f18
       )

    jac[jf18, jo17] = (
       +rho*Y[jp]*rate_eval.p_o17__f18
       )

    jac[jf18, jf18] = (
       -rate_eval.f18__o18__weak__wc12
       -rho*Y[jp]*rate_eval.p_f18__he4_o15
       )

    jac[jf18, jne18] = (
       +rate_eval.ne18__f18__weak__wc12
       )

    jac[jf19, jp] = (
       -rho*Y[jf19]*rate_eval.p_f19__he4_o16
       +rho*Y[jo18]*rate_eval.p_o18__f19
       )

    jac[jf19, jo18] = (
       +rho*Y[jp]*rate_eval.p_o18__f19
       )

    jac[jf19, jf19] = (
       -rho*Y[jp]*rate_eval.p_f19__he4_o16
       )

    jac[jne18, jp] = (
       +rho*Y[jf17]*rate_eval.p_f17__ne18
       )

    jac[jne18, jf17] = (
       +rho*Y[jp]*rate_eval.p_f17__ne18
       )

    jac[jne18, jne18] = (
       -rate_eval.ne18__f18__weak__wc12
       )

    return jac

Now we can import the network that was just created

import cno_integration_example as cno
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[6], line 1
----> 1 import cno_integration_example as cno

File ~/work/comp_astro_tutorial/comp_astro_tutorial/content/reaction_networks/cno_integration_example.py:1
----> 1 import numba
      2 import numpy as np
      3 from numba.experimental import jitclass

ModuleNotFoundError: No module named 'numba'

We’ll use the BDF solver from SciPy

from scipy.integrate import solve_ivp

Now we’ll set the thermodynamic conditions. We initialize mass fractions and then convert to molar fractions, since that’s what the RHS uses

rho = 150
T = 1.5e7

X0 = np.zeros(cno.nnuc)
X0[cno.ip] = 0.7
X0[cno.ihe4] = 0.28
X0[cno.ic12] = 0.02

Y0 = X0/cno.A
Y0
array([0.7       , 0.07      , 0.00166667, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        ])
tmax = 1.e20

sol = solve_ivp(cno.rhs, [0, tmax], Y0, method="BDF",
                dense_output=True, args=(rho, T), rtol=1.e-6, atol=1.e-6)
sol
  message: 'The solver successfully reached the end of the integration interval.'
     nfev: 345
     njev: 20
      nlu: 63
      sol: <scipy.integrate._ivp.common.OdeSolution object at 0x7f54490ee5b0>
   status: 0
  success: True
        t: array([0.00000000e+00, 1.00000000e-04, 2.00000000e-04, 1.20000000e-03,
       2.20000000e-03, 1.22000000e-02, 2.22000000e-02, 1.22200000e-01,
       2.22200000e-01, 1.22220000e+00, 2.22220000e+00, 1.22222000e+01,
       2.22222000e+01, 1.22222200e+02, 2.22222200e+02, 1.22222220e+03,
       2.22222220e+03, 1.22222222e+04, 2.22222222e+04, 1.22222222e+05,
       2.22222222e+05, 1.22222222e+06, 2.22222222e+06, 1.22222222e+07,
       2.22222222e+07, 1.22222222e+08, 2.22222222e+08, 1.22222222e+09,
       2.22222222e+09, 1.22222222e+10, 2.22222222e+10, 1.22222222e+11,
       2.22222222e+11, 1.18930639e+12, 2.15639055e+12, 3.12347472e+12,
       5.88239225e+12, 8.64130978e+12, 1.14002273e+13, 1.41591448e+13,
       1.91252701e+13, 2.40913954e+13, 2.90575207e+13, 3.40236460e+13,
       3.89897713e+13, 4.68497332e+13, 5.47096950e+13, 6.25696569e+13,
       7.04296187e+13, 7.82895806e+13, 9.11122132e+13, 1.03934846e+14,
       1.16757478e+14, 1.29580111e+14, 1.46986055e+14, 1.64391999e+14,
       1.81797943e+14, 1.99203887e+14, 2.16609830e+14, 2.64882217e+14,
       3.13154604e+14, 3.61426990e+14, 4.09699377e+14, 4.57971763e+14,
       5.46800147e+14, 6.35628531e+14, 7.24456914e+14, 8.13285298e+14,
       1.10221886e+15, 1.39115243e+15, 1.68008599e+15, 4.56942165e+15,
       7.45875730e+15, 1.91689901e+16, 3.08792230e+16, 4.25894558e+16,
       8.46062725e+16, 1.26623089e+17, 1.68639906e+17, 2.41071575e+17,
       3.13503243e+17, 3.85934912e+17, 4.58366581e+17, 5.70970421e+17,
       6.83574262e+17, 7.96178103e+17, 9.08781944e+17, 1.02138578e+18,
       1.19974740e+18, 1.37810902e+18, 1.55647063e+18, 1.73483225e+18,
       1.91319387e+18, 2.11506050e+18, 2.31692714e+18, 2.51879378e+18,
       2.72066042e+18, 2.92252705e+18, 3.12439369e+18, 3.39568748e+18,
       3.66698128e+18, 3.93827507e+18, 4.20956886e+18, 4.48086266e+18,
       4.75215645e+18, 5.09868743e+18, 5.44521840e+18, 5.79174937e+18,
       6.13828035e+18, 6.48481132e+18, 6.83134229e+18, 7.22834613e+18,
       7.62534996e+18, 8.02235380e+18, 8.41935763e+18, 8.81636147e+18,
       9.29499887e+18, 9.77363628e+18, 1.02522737e+19, 1.07309111e+19,
       1.12095485e+19, 1.16881859e+19, 1.23607969e+19, 1.30334079e+19,
       1.37060188e+19, 1.43786298e+19, 1.50512408e+19, 1.57238518e+19,
       1.65943192e+19, 1.74647866e+19, 1.83352541e+19, 1.92057215e+19,
       2.00761889e+19, 2.06423325e+19, 2.12084761e+19, 2.17746197e+19,
       2.23407633e+19, 2.65290211e+19, 3.07172789e+19, 3.49055367e+19,
       7.67881147e+19, 1.00000000e+20])
 t_events: None
        y: array([[ 7.00000000e-001,  7.00000000e-001,  7.00000000e-001, ...,
         1.96850485e-008,  5.17746174e-009,  2.78572823e-010],
       [ 7.00000000e-002,  7.00000000e-002,  7.00000000e-002, ...,
         2.44132876e-001,  2.44132892e-001,  2.44132894e-001],
       [ 1.66666667e-003,  1.66666667e-003,  1.66666667e-003, ...,
         3.11241160e-006,  3.11622075e-006,  3.11654096e-006],
       ...,
       [ 0.00000000e+000,  8.19119797e-124,  5.42621931e-123, ...,
         2.71592977e-027,  7.14328637e-028,  3.84344694e-029],
       [ 0.00000000e+000,  3.46660092e-153,  3.09799299e-152, ...,
         1.27460238e-009,  1.27460252e-009,  1.27460254e-009],
       [ 0.00000000e+000,  1.09799942e-119,  6.00420323e-119, ...,
        -9.67611878e-055, -1.56368393e-055, -4.07296560e-060]])
 y_events: None

Now we can plot the mass fractions.

fig = plt.figure()
ax = fig.add_subplot(111)

for n in range(cno.nnuc):
    ax.loglog(sol.t, sol.y[n,:] * cno.A[n], label=f"X({cno.names[n].capitalize()})")

ax.set_xlim(1.e10, 1.e20)
ax.set_ylim(1.e-8, 1.0)
ax.legend(fontsize="small")

fig.set_size_inches((10, 8))
../_images/integration-example_19_0.png