Skip to content

The simple.ccsne Namespace

The ccsne submodule contains everything related to working with CCSNe models.


simple.ccsne.ccsne_zones module-attribute

ccsne_zones = ('Mrem', 'Ni', 'Si', 'O/Si', 'O/Ne', 'O/C', 'He/C', 'He/N', 'H')

The different shells of the onion structure going outwards.

simple.ccsne.z_names module-attribute

z_names = ['Neut', 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U']

The element symbol for each atomic number (z) up to U

simple.ccsne.CCSNe

CCSNe(name, reference_models_=None, **hdf5_attrs)

Bases: ModelBase

Model for CCSNe yields and their mass coordinates.

Create a model instance and populate hdf5_attrs.

Source code in simple/models.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
def __init__(self, name, reference_models_ = None, **hdf5_attrs):
    """Create a model instance and populate ``hdf5_attrs``."""
    super().__setattr__('name', name)
    super().__setattr__('hdf5_attrs', HDF5Dict())
    super().__setattr__('normal_attrs', NamedDict())
    super().__setattr__('_hash', hash(object()))

    for attr in self.REQUIRED_ATTRS:
        if attr not in hdf5_attrs:
            raise ValueError(f"{{Required attribute '{attr}' does not exist in initialisation arguments}}")

    for k, v in hdf5_attrs.items():
        self.setattr(k, v, hdf5_compatible=True)

    # This is how we know which class to create. Need to be set after attrs incase you remapp the
    # class name
    self.setattr('clsname', self.__class__.__name__, hdf5_compatible=True, overwrite=True)

    # Automatically creates <name> key array if <name>_values and <name>_keys exists
    if self.VALUES_KEYS_TO_ARRAY:
        for key in self.hdf5_attrs:
            if key[-7:] == '_values':
                aattr = key[:-7]
                vattr = key
                kattr = key[:-7] + '_keys'
                if kattr in self.hdf5_attrs and aattr not in self.hdf5_attrs:
                    v = self.hdf5_attrs[vattr]
                    k = self.hdf5_attrs[kattr]
                    self.setattr(aattr, utils.askeyarray(v, k), hdf5_compatible=False)

    for key, value in self.hdf5_attrs.items():
        if key[:6] == 'refid_':
            id_name = key[6:]
            if reference_models_ is None:
                raise ValueError(f"No reference models were supplied")
            for ref in reference_models_:
                if ref.name == value:
                    self.setattr(f'ref_{id_name}', ref, hdf5_compatible=False)
                    break
            else:
                raise ValueError(f"No model called '{value}' was found in the supplied reference models")

ABUNDANCE_KEYARRAY class-attribute instance-attribute

ABUNDANCE_KEYARRAY = 'abundance'

REPR_ATTRS class-attribute instance-attribute

REPR_ATTRS = ['name', 'type', 'dataset', 'mass']

REQUIRED_ATTRS class-attribute instance-attribute

REQUIRED_ATTRS = ['type', 'dataset', 'citation', 'mass', 'masscoord', 'masscoord_mass', 'abundance_values', 'abundance_keys', 'abundance_unit', 'refid_isoabu', 'refid_isomass']

masscoord_label class-attribute instance-attribute

masscoord_label = 'Mass Coordinate [solar masses]'

masscoord_label_latex class-attribute instance-attribute

masscoord_label_latex = 'Mass Coordinate [M${}_{\\odot}$]'

masscoord_mass_label class-attribute instance-attribute

masscoord_mass_label = 'Coordinate Mass [solar masses]'

masscoord_mass_label_latex class-attribute instance-attribute

masscoord_mass_label_latex = 'Coordinate Mass [M${}_{\\odot}$]'

simple.ccsne.add_weights_ccsne

add_weights_ccsne(modeldata, axis, weights=1, kwargs=None)

Add weights to the specified axis of each CCSNe datapoint in the modeldata dictionary.

Before normalisation, if applied, the weight of each datapoint will be multiplied by the mass of each mass coordinate.

This function appends a new array of weights (under axisname) to each datapoint in modeldata. The weights can be a constant or a string referring to data to be indvidually retrieved from each model. Optionally, the weights can be summed, normalized, and masked for missing data.

The mask and mask_na arguments should be the same as those used to generate 'modeldata' to ensure conistent results.

Add weights to CCSNe datapoints by combining standard weighting with mass coordinate scaling.

This function extends add_weights by additionally multiplying the resulting weights by the mass associated with each mass coordinate in CCSNe models.

The initial weighting follows the same logic as add_weights, accepting either a scalar or a string referring to model attributes. The 'mask' and 'mask_na' arguments should match those used when creating modeldata to ensure consistency.

Parameters:

  • modeldata (dict) –

    The data dictionary as returned by get_data, structured as {model_name: list of datapoints}. Each datapoint is a dictionary.

  • axis (str) –

    The axis key to which the weights apply (e.g., 'x', 'y').

  • weights (int, float, or str, default: 1 ) –

    The weight specification. Can be: - A scalar to apply uniformly across all datapoints, - A string key to retrieve values from each model individually.

  • **kwargs

    Any valid keyword arguments for the add_weights function.

Returns:

  • dict

    The modified modeldata, with weight arrays added to each datapoint.

Source code in simple/ccsne.py
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
@utils.set_default_kwargs(inherits_=plotting.add_weights)
def add_weights_ccsne(modeldata, axis, weights = 1, kwargs=None):
    """
    Add weights to the specified axis of each CCSNe datapoint in the modeldata dictionary.

    Before normalisation, if applied, the weight of each datapoint will be multiplied by the
    mass of each mass coordinate.

    This function appends a new array of weights (under `axisname`) to each datapoint
    in `modeldata`. The weights can be a constant or a string referring to data to be indvidually
    retrieved from each model. Optionally, the weights can be summed,
    normalized, and masked for missing data.

    The `mask` and `mask_na` arguments should be the same as those used to generate 'modeldata' to ensure
    conistent results.

    Add weights to CCSNe datapoints by combining standard weighting with mass coordinate scaling.

    This function extends `add_weights` by additionally multiplying the resulting weights
    by the mass associated with each mass coordinate in CCSNe models.

    The initial weighting follows the same logic as `add_weights`, accepting either a scalar
    or a string referring to model attributes. The 'mask' and 'mask_na' arguments should
    match those used when creating `modeldata` to ensure consistency.

    Args:
        modeldata (dict): The data dictionary as returned by `get_data`, structured as
            {model_name: list of datapoints}. Each datapoint is a dictionary.
        axis (str): The axis key to which the weights apply (e.g., 'x', 'y').
        weights (int, float, or str): The weight specification. Can be:
            - A scalar to apply uniformly across all datapoints,
            - A string key to retrieve values from each model individually.
        **kwargs: Any valid keyword arguments for the `add_weights` function.

    Returns:
        dict: The modified `modeldata`, with weight arrays added to each datapoint.
    """

    mask = kwargs.get('mask', None)
    mask_na = kwargs.get('mask_na', True)
    axisname = kwargs.get('axisname', 'w')

    norm_weights = kwargs.pop('norm_weights', True) # Defaults to the value of add_weights
    kwargs['norm_weights'] = False
    modeldata = plotting.add_weights(modeldata, axis, weights, kwargs=kwargs)

    logger.info('Multiplying all weights by the mass coordinate mass')

    for model, datapoints in modeldata.items():
        masscoord_mass = model.masscoord_mass
        if mask:
            imask = model.get_mask(mask)

        for ki, datapoint in enumerate(datapoints):
            if mask and not mask_na:
                datapoint[axisname] = datapoint[axisname] * masscoord_mass[imask]
            else:
                datapoint[axisname] = datapoint[axisname] * masscoord_mass

    if norm_weights:
        plotting._norm_weights(modeldata, axisname)

    return modeldata

simple.ccsne.calc_default_onion_structure

calc_default_onion_structure(abundance, keys, masscoord)

Calculated the boundaries of different layers within the CCSNe onion structure.

Note This function is calibrated for the initial set of CCSNe models and might not be applicable to other models.

The returned array contains the index of the lower bound of the given layer. If a layer is not found the index is given as np.nan.

Source code in simple/ccsne.py
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def calc_default_onion_structure(abundance, keys, masscoord): # pragma: no cover
    """
    Calculated the boundaries of different layers within the CCSNe onion structure.

    **Note** This function is calibrated for the initial set of CCSNe models and might not be applicable to
    other models.

    The returned array contains the index of the lower bound of the given layer. If a layer is not found the index is
    given as np.nan.
    """
    abundance = np.asarray(abundance)
    masscoord = np.asarray(masscoord)
    mass = masscoord
    he4 = abundance[:, keys.index('He-4')]
    c12 = abundance[:, keys.index('C-12')]
    ne20 = abundance[:, keys.index('Ne-20')]
    o16 = abundance[:, keys.index('O-16')]
    si28 = abundance[:, keys.index('Si-28')]
    n14 = abundance[:, keys.index('N-14')]
    ni56 = abundance[:, keys.index('Ni-56')]

    masscut = mass[0]
    massmax = mass[-1]
    logger.info('Calculating Default Onion Structure')
    logger.debug("m_cut: " + str(masscut))
    logger.debug("massmax: " + str(massmax))

    zones = 'H He/N He/C O/C O/Ne O/Si Si Ni'.split()
    boundaries = []

    # This code works for most but not all of the 18 models in the original release
    # So use with caution

    # definition of borders
    ih = np.where((he4 > 0.5))[0][-1]
    logger.debug("Lower boundary of the H shell: " + str(mass[ih]))
    boundaries.append(ih)

    ihe1 = np.where((n14 > o16) & (n14 > c12) & (n14 > 1.e-3))[0][0]
    ihe_check = np.searchsorted(mass, mass[ihe1] + 0.005)
    if ihe_check < len(mass) and not (
            (n14[ihe_check] > o16[ihe_check]) and (n14[ihe_check] > c12[ihe_check]) and (n14[ihe_check] > 1.e-3)):
        ihe1 = np.where((n14 > o16) & (n14 > c12) & (n14 > 1.e-3) & (mass >= mass[ihe1] + 0.005))[0][0]
    logger.debug("Lower boundary of the He/N shell: " + str(mass[ihe1]))
    boundaries.append(ihe1)

    ihe = np.where((c12 > he4) & (mass <= mass[ih]))[0][-1]
    logger.debug("Lower boundary of the He/C shell: " + str(mass[ihe]))
    boundaries.append(ihe)

    ic2 = np.where((c12 > ne20) & (si28 < c12) & (c12 > 8.e-2))[0][0]
    logger.debug("Lower boundary of the O/C shell: " + str(mass[ic2]))
    boundaries.append(ic2)

    ine = np.where((ne20 > 1.e-3) & (si28 < ne20) & (ne20 > c12))[0][0]
    if ine > ic2:
        ine = ic2
    logger.debug("Lower boundary of the O/Ne shell: " + str(mass[ine]))
    boundaries.append(ine)

    io = np.where((si28 < o16) & (o16 > 5.e-3))[0][0]
    logger.debug("Lower boundary of the O/Si layer: " + str(mass[io]))
    boundaries.append(io)

    try:
        indices = np.where(ni56 > si28)[0]
        if indices.size > 0:
            isi = indices[-1]
        else:
            if ni56[1] < si28[1] and si28[1] > o16[1]:
                isi = 0
            else:
                raise IndexError("No suitable boundary found")
    except IndexError:
        logger.debug("No lower boundary of Si layer")
        boundaries.append(-1)
    else:
        if len(mass[isi:io]) < 2:
            boundaries.append(-1)
            logger.debug("No lower boundary of Si layer")
        else:
            logger.debug(f"Lower boundary of the Si layer: {mass[isi]}")
            boundaries.append(isi)

    try:
        ini = np.where((ni56 > si28))[0][0]
    except IndexError:
        logger.debug("No lower boundary of Ni layer")
        boundaries.append(-1)
    else:
        logger.debug("Lower boundary of the Ni layer: " + str(mass[ini]))
        boundaries.append(ini)

    onion_lbounds =  utils.askeyarray(boundaries, zones, dtype=np.int64)

    zone = np.full(masscoord.shape, 'Undefined')
    keys = onion_lbounds.dtype.names
    ubound = None
    for key in keys:
        lbound = int(onion_lbounds[key][0])
        if lbound >= 0:
            zone[slice(lbound, ubound)] = key
            ubound = lbound

        # Remnant is everything inside the lowermost shell.
        zone[slice(None, ubound)] = 'Mrem'

    return zone

simple.ccsne.fudge_masscord_mass

fudge_masscord_mass(masscoord)

Estimate the mass at each mass coordinate.

The mass associated with a coordinate is approximated by the difference between successive coordinates. The last value is duplicated so the output has the same length as the input array.

Source code in simple/ccsne.py
54
55
56
57
58
59
60
61
62
63
64
65
def fudge_masscord_mass(masscoord): # pragma: no cover
    """
    Estimate the mass at each mass coordinate.

    The mass associated with a coordinate is approximated by the difference
    between successive coordinates. The last value is duplicated so the output
    has the same length as the input array.
    """
    logger.info('Fudging the masscoord mass from masscoord')
    masscoord = np.asarray(masscoord)
    masscoord = masscoord[1:] - masscoord[:1]
    return np.append(masscoord, masscoord[-1])

simple.ccsne.hist_ccsne

hist_ccsne(models, xkey=None, ykey=None, weights=1, r=None, kwargs=None)

CCSNe implementation of hist. See this function for more details and a description of the optional arguments.

Note Weights are calculated using add_weights_ccsne where each weight is multiplied by the mass associated with each mass coordinate in CCSNe models.

Source code in simple/ccsne.py
817
818
819
820
821
822
823
824
825
826
827
828
829
@utils.set_default_kwargs(inherits_=plotting.hist,
                                  weights_default_attrname='abundance', weights_unit='mass',
                                  )
def hist_ccsne(models, xkey=None, ykey=None, weights=1, r=None, kwargs=None):
    """
    CCSNe implementation of [`hist`][simple.hist]. See this function for more details and a
    description of the optional arguments.

    **Note** Weights are calculated using [`add_weights_ccsne`][simple.ccsne.add_weights_ccsne] where each
    weight is multiplied by the mass associated with each mass coordinate in CCSNe models.
    """
    kwargs.setdefault('SIMPLE_add_weights', add_weights_ccsne)
    return plotting.hist(models, xkey, ykey, weights=weights, r=r, kwargs=kwargs)

simple.ccsne.load_LC18

load_LC18(data_dir, ref_isoabu, ref_isomass, remove_Mrem=False)

Load the CCSNe models from Limongi & Chieffi (2018).

Source code in simple/ccsne.py
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
def load_LC18(data_dir, ref_isoabu, ref_isomass, remove_Mrem = False): # pragma: no cover
    """Load the CCSNe models from Limongi & Chieffi (2018)."""
    def load(emass, model_name):
        filename = data_dir + model_name
        # print(filename)
        with open(filename, 'r') as f:
            # getting isotopes, not first header names, and final ye and spooky abundances (group of isolated isotopes,
            # probably sorted with artificial reactions handling mass conservation or sink particles approach)
            head = f.readline();
            isos_dum = head.split()[4:-skip_heavy_]
            # correcting names to get H1 (and the crazy P and A)
            isos_dum[0] = isos_dum[0] + '1';
            isos_dum[1] = isos_dum[1] + '1';
            isos_dum[6] = isos_dum[6] + '1'
            dum_a = [re.findall('\d+', ik)[0] for ik in isos_dum]  # getting the A from isotope prefixes
            dum_el = [re.sub(r'[0-9]+', '', ik) for ik in
                      isos_dum]  # getting the element prefixes from the isotope prefixes
            dum_new_iso = [dum_el[ik].capitalize() + '-' + dum_a[ik] for ik in range(len(isos_dum))]

            data = f.readlines()[:-1]  # getting the all item, excepting the last fake line (bounch of zeros)

            masscoord = np.array([float(ii.split()[0]) for ii in data])

            # isotope prefixes that we can use around, just neutron prefixes is different, but not care
            keys = utils.asisotopes(dum_new_iso, allow_invalid=True)

            # done reading, just closing the file now
            # converting in Msun too.
            abu = np.asarray([row.split()[4:-skip_heavy_] for row in data], dtype=np.float64)
            unit = 'mass'

            masscoord_mass = fudge_masscord_mass(masscoord)

            data = dict(type='CCSNe', dataset=dataset, citation=citation,
                        refid_isoabu=ref_isoabu, refid_isomass=ref_isomass,
                        mass=int(emass), masscoord=np.asarray(masscoord),
                        masscoord_mass=np.asarray(masscoord_mass),
                        abundance_values=np.asarray(abu), abundance_keys=keys,
                        abundance_unit=unit)

            data['zone'] = calc_default_onion_structure(abu, keys, masscoord)
            if np.any(data['zone'] == 'Mrem'):
                if remove_Mrem:
                    logger.info(f"Removing datapoints from the Mrem zone")
                    keep = data['zone'] != 'Mrem'
                    data['zone'] = np.ascontiguousarray(data['zone'][keep])
                    data['masscoord'] = np.ascontiguousarray(data['masscoord'][keep])
                    data['masscoord_mass'] = np.ascontiguousarray(data['masscoord_mass'][keep])
                    data['abundance_values'] = np.ascontiguousarray(data['abundance_values'][keep])
                else:
                    logger.warning(f"Model contains data points from the Mrem zone.")

            models[f"{dataset}_m{emass}"] = data
            return data

    skip_heavy_ = 43  # used to skip final ye and spooky abundances (see below)
    dataset = 'LC18'
    citation = ''
    models = {}

    # 15
    load('15', '015a000.dif_iso_nod')

    # 20
    load('20', '020a000.dif_iso_nod')

    # 25
    load('25', '025a000.dif_iso_nod')

    return models

simple.ccsne.load_La22

load_La22(data_dir, ref_isoabu, ref_isomass, remove_Mrem=False)

Load the CCSNe models from Lawson et al. (2022).

Source code in simple/ccsne.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def load_La22(data_dir, ref_isoabu, ref_isomass, remove_Mrem = False): # pragma: no cover
    """Load the CCSNe models from Lawson et al. (2022)."""
    def load(emass, model_name):
        mass_lines = []
        with open(data_dir + model_name, "rt") as f:
            for ln, line in enumerate(f):
                if 'mass enclosed' in line:
                    mass_lines.append(line)
        mass = [float(row.split()[3]) for row in mass_lines]
        numpart = [int(row.split()[0][1:]) for row in mass_lines]
        number_of_parts = len(numpart)  # number of particles (it may change from model1 to model1)
        # print('# particles = ',number_of_parts)

        # open and read abundances for all trajectories
        a, x, z, iso_name = [], [], [], []
        with open(data_dir + model_name, "rt") as f:
            i = 0
            while i < number_of_parts:
                f.readline();
                f.readline();
                j = 0
                a_i, x_i, z_i, iso_i = [], [], [], []
                while j < num_species:
                    line = f.readline().split()
                    a_i.append(int(line[0]))
                    z_i.append(int(line[1]))
                    x_i.append(float(line[2]))
                    iso_i.append(f"{z_names[int(line[1])]}-{line[0]}")
                    j += 1
                a.append(a_i);
                z.append(z_i);
                x.append(x_i);
                iso_name.append(iso_i)
                i += 1

                # Assumes all trajectories have the same isotope list but not necessarily ordered the same
        y = {}
        for i in range(number_of_parts):
            for j, iso in enumerate(iso_name[i]):
                y.setdefault(iso, [])
                y[iso].append(x[i][j])
        dum_ab = np.array([list(v) for v in y.values()])

        # If iso is identical for all trajectories this is another way to do it
        # y = np.transpose([[x[i][j] for j in range(len(iso[i])] for i in range(number_of_parts)])

        masscoord = mass
        keys = utils.asisotopes(y.keys(), allow_invalid=True)
        abu = np.transpose([list(v) for v in y.values()])
        unit = 'mass'

        masscoord_mass = fudge_masscord_mass(masscoord)

        data = dict(type='CCSNe', dataset=dataset, citation=citation,
                    refid_isoabu=ref_isoabu, refid_isomass=ref_isomass,
                    mass=int(emass), masscoord=np.asarray(masscoord),
                    masscoord_mass=np.asarray(masscoord_mass),
                    abundance_values=np.asarray(abu), abundance_keys=keys,
                    abundance_unit=unit)

        data['zone'] = calc_default_onion_structure(abu, keys, masscoord)
        if np.any(data['zone'] == 'Mrem'):
            if remove_Mrem:
                logger.info(f"Removing datapoints from the Mrem zone")
                keep = data['zone'] != 'Mrem'

                data['zone'] = np.ascontiguousarray(data['zone'][keep])
                data['masscoord'] = np.ascontiguousarray(data['masscoord'][keep])
                data['masscoord_mass'] = np.ascontiguousarray(data['masscoord_mass'][keep])
                data['abundance_values'] = np.ascontiguousarray(data['abundance_values'][keep])
            else:
                logger.warning(f"Model contains data points from the Mrem zone.")

        models[f'{dataset}_m{emass}'] = data
        return data

    num_species = 5209
    dataset = 'La22'
    citation = ''
    models = {}


    # 15
    load('15','M15s_run15f1_216M1.3bgl_mp.txt')

    # 20
    load('20', 'M20s_run20f1_300M1.56jl_mp.txt')

    # 25
    load('25', 'M25s_run25f1_280M1.83rrl_mp.txt')

    return models

simple.ccsne.load_Pi16

load_Pi16(fol2mod, ref_isoabu, ref_isomass, remove_Mrem=False)

Load the CCSNe models from Pignatari et al. (2016).

Source code in simple/ccsne.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
def load_Pi16(fol2mod, ref_isoabu, ref_isomass, remove_Mrem = False): # pragma: no cover
    """Load the CCSNe models from Pignatari et al. (2016)."""
    from nugridpy import nugridse as mp
    def load(emass, modelname):
        pt_exp = mp.se(fol2mod, modelname, rewrite=True)
        cyc = pt_exp.se.cycles[-1]
        t9_cyc = pt_exp.se.get(cyc, 'temperature')
        mass = pt_exp.se.get(cyc, 'mass')

        ejected = np.where(t9_cyc < 1.1e-10)[0][0]

        masscoord = pt_exp.se.get(cyc, 'mass')[ejected:]
        abu = pt_exp.se.get(cyc, 'iso_massf')[ejected:]
        unit = 'mass'
        keys = utils.asisotopes(pt_exp.se.isotopes, allow_invalid=True)

        masscoord_mass = fudge_masscord_mass(masscoord)

        data = dict(type='CCSNe', dataset=dataset, citation=citation,
                    refid_isoabu=ref_isoabu, refid_isomass=ref_isomass,
                    mass=int(emass), masscoord=np.asarray(masscoord),
                    masscoord_mass=np.asarray(masscoord_mass),
                    abundance_values=np.asarray(abu), abundance_keys=keys,
                    abundance_unit=unit)

        data['zone'] = calc_default_onion_structure(abu, keys, masscoord)
        if np.any(data['zone'] == 'Mrem'):
            if remove_Mrem:
                logger.info(f"Removing datapoints from the Mrem zone")
                keep = data['zone'] != 'Mrem'
                data['zone'] = np.ascontiguousarray(data['zone'][keep])
                data['masscoord'] = np.ascontiguousarray(data['masscoord'][keep])
                data['masscoord_mass'] = np.ascontiguousarray(data['masscoord_mass'][keep])
                data['abundance_values'] = np.ascontiguousarray(data['abundance_values'][keep])
            else:
                logger.warning(f"Model contains data points from the Mrem zone.")

        models[f'{dataset}_m{emass}'] = data
        return data

    dataset = 'Pi16'
    citation = ''
    models = {}


    with mute_stdout():
        # 15Msun
        load('15', 'M15.0')

        # 20Msun
        load('20', 'M20.0')

        # 25Msun
        load('25', 'M25.0')

    return models

simple.ccsne.load_Ra02

load_Ra02(data_dir, ref_isoabu, ref_isomass, remove_Mrem=False)

Load the CCSNe models from Rauscher et al. (2002).

Source code in simple/ccsne.py
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
def load_Ra02(data_dir, ref_isoabu, ref_isomass, remove_Mrem = False): # pragma: no cover
    """Load the CCSNe models from Rauscher et al. (2002)."""
    def load(emass, model_name):
        filename = data_dir + model_name
        # print(filename)
        with open(filename, 'r') as f:
            head = f.readline();
            isos_dum = head.split()[5:]  # getting isotopes, not first header names
            dum_a = [re.findall('\d+', ik)[0] for ik in isos_dum]  # getting the A from isotope prefixes
            dum_el = [re.sub(r'[0-9]+', '', ik) for ik in
                      isos_dum]  # getting the element prefixes from the isotope prefixes
            dum_new_iso = [dum_el[ik].capitalize() + '-' + dum_a[ik] for ik in range(len(isos_dum))]

            # isotope prefixes that we can use around, just neutron prefixes is different, but not care
            keys = utils.asisotopes(dum_new_iso, allow_invalid=True)

            data = f.readlines()[:-2]  # getting the all item, excepting the last two lines
            # rau_mass.append(dum) # converting in Msun too.
            abu = np.asarray([row.split()[3:] for row in data], np.float64)
            unit = 'mass'

            masscoord = np.array([float(ii.split()[1]) / 1.989e+33 for ii in data])
            masscoord_mass = np.array([float(ii.split()[2]) / 1.989e+33 for ii in data])
            #masscoord_mass = fudge_masscord_mass(masscoord)

            data = dict(type='CCSNe', dataset=dataset, citation=citation,
                        refid_isoabu=ref_isoabu, refid_isomass=ref_isomass,
                        mass=int(emass), masscoord=np.asarray(masscoord),
                        masscoord_mass=np.asarray(masscoord_mass),
                        abundance_values=np.asarray(abu), abundance_keys=keys,
                        abundance_unit=unit)

            data['zone'] = calc_default_onion_structure(abu, keys, masscoord)
            if np.any(data['zone'] == 'Mrem'):
                if remove_Mrem:
                    logger.info(f"Removing datapoints from the Mrem zone")
                    keep = data['zone'] != 'Mrem'
                    data['zone'] = np.ascontiguousarray(data['zone'][keep])
                    data['masscoord'] = np.ascontiguousarray(data['masscoord'][keep])
                    data['masscoord_mass'] = np.ascontiguousarray(data['masscoord_mass'][keep])
                    data['abundance_values'] = np.ascontiguousarray(data['abundance_values'][keep])
                else:
                    logger.warning(f"Model contains data points from the Mrem zone.")

            models[f"{dataset}_m{emass}"] = data
            return data

    dataset = 'Ra02'
    citation = ''
    models = {}

    # 15
    load('15', 's15a28c.expl_comp')

    # 20
    load('20', 's20a28n.expl_comp')

    # 25
    load('25', 's25a28d.expl_comp')

    return models

simple.ccsne.load_Ri18

load_Ri18(fol2mod, ref_isoabu, ref_isomass, remove_Mrem=False)

Load the CCSNe models from Ritter et al. (2018).

Source code in simple/ccsne.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def load_Ri18(fol2mod, ref_isoabu, ref_isomass, remove_Mrem = False): # pragma: no cover
    """Load the CCSNe models from Ritter et al. (2018)."""
    from nugridpy import nugridse as mp
    def load(emass, modelname):
        pt_exp = mp.se(fol2mod, modelname, rewrite=True)
        cyc = pt_exp.se.cycles[-1]
        t9_cyc = pt_exp.se.get(cyc, 'temperature')
        mass = pt_exp.se.get(cyc, 'mass')

        ejected = np.where(np.array(t9_cyc) > 1.1e-9)[0][0]

        masscoord = pt_exp.se.get(cyc, 'mass')[ejected:]
        abu = np.array(pt_exp.se.get(cyc, 'iso_massf'))[ejected:]
        unit = 'mass'
        keys = utils.asisotopes(pt_exp.se.isotopes, allow_invalid=True)

        masscoord_mass = fudge_masscord_mass(masscoord)

        data = dict(type='CCSNe', dataset=dataset, citation=citation,
                    refid_isoabu=ref_isoabu, refid_isomass=ref_isomass,
                    mass=int(emass), masscoord=np.asarray(masscoord),
                    masscoord_mass = np.asarray(masscoord_mass),
                    abundance_values=np.asarray(abu), abundance_keys=keys,
                    abundance_unit=unit)

        data['zone'] = calc_default_onion_structure(abu, keys, masscoord)
        if np.any(data['zone'] == 'Mrem'):
            if remove_Mrem:
                logger.info(f"Removing datapoints from the Mrem zone")
                keep = data['zone'] != 'Mrem'
                data['zone'] = np.ascontiguousarray(data['zone'][keep])
                data['masscoord'] = np.ascontiguousarray(data['masscoord'][keep])
                data['masscoord_mass'] = np.ascontiguousarray(data['masscoord_mass'][keep])
                data['abundance_values'] = np.ascontiguousarray(data['abundance_values'][keep])
            else:
                logger.warning(f"Model contains data points from the Mrem zone.")

        models[f'{dataset}_m{emass}'] = data
        return data

    dataset = 'Ri18'
    citation = ''
    models = {}

    with mute_stdout():
        # 15Msun
        load('15', 'M15.0Z2.0e-02.Ma.0020601.out.h5')

        # 20Msun
        load('20', 'M20.0Z2.0e-02.Ma.0021101.out.h5')

        # 25Msun
        load('25', 'M25.0Z2.0e-02.Ma.0023601.out.h5')

    return models

simple.ccsne.load_Si18

load_Si18(data_dir, ref_isoabu, ref_isomass, decayed=False, remove_Mrem=False)

Load the CCSNe models from Sieverding et al. (2018).

Source code in simple/ccsne.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
def load_Si18(data_dir, ref_isoabu, ref_isomass, decayed=False, remove_Mrem = False): # pragma: no cover
    """Load the CCSNe models from Sieverding et al. (2018)."""
    def load(emass, file_sie):
        with h5py.File(data_dir + file_sie) as data_file:
            data = data_file["post-sn"]

            # need to decode binary isotope names to get strings
            iso_list_sie = [name.decode() for name in data["isotopes"]]
            mr = list(data["mass_coordinates_sun"])

            results = {}
            for jiso, iso in enumerate(iso_list_sie):
                if decayed:
                    results[iso] = data["mass_fractions_decayed"][:, jiso]
                else:
                    results[iso] = data["mass_fractions"][:, jiso]

        masscoord = np.array(mr)
        keys = utils.asisotopes(results.keys(), allow_invalid=True)
        abu = np.transpose([list(v) for v in results.values()])
        unit = 'mass'

        masscoord_mass = fudge_masscord_mass(masscoord)

        data = dict(type='CCSNe', dataset=dataset, citation=citation,
                    refid_isoabu=ref_isoabu, refid_isomass=ref_isomass,
                    mass=int(emass), masscoord=np.asarray(masscoord),
                    masscoord_mass=np.asarray(masscoord_mass),
                    abundance_values=np.asarray(abu), abundance_keys=keys,
                    abundance_unit=unit)

        data['zone'] = calc_default_onion_structure(abu, keys, masscoord)
        if np.any(data['zone'] == 'Mrem'):
            if remove_Mrem:
                logger.info(f"Removing datapoints from the Mrem zone")
                keep = data['zone'] != 'Mrem'
                data['zone'] = np.ascontiguousarray(data['zone'][keep])
                data['masscoord'] = np.ascontiguousarray(data['masscoord'][keep])
                data['masscoord_mass'] = np.ascontiguousarray(data['masscoord_mass'][keep])
                data['abundance_values'] = np.ascontiguousarray(data['abundance_values'][keep])
            else:
                logger.warning(f"Model contains data points from the Mrem zone.")

        models[f'{dataset}_m{emass}'] = data
        return data

    dataset = 'Si18'
    citation = ''
    models = {}

    # 15
    load('15', "s15_data.hdf5")

    # 20
    load('20', "s20_data.hdf5")

    # 25
    load('25', "s25_data.hdf5")

    return models

simple.ccsne.mute_stdout

mute_stdout()
Source code in simple/ccsne.py
50
51
52
def mute_stdout(): # pragma: no cover
    # Mute the nugrid messages
    return contextlib.redirect_stdout(io.StringIO())

simple.ccsne.plot_ccsne

plot_ccsne(models, ykey, *, semilog=False, onion=None, kwargs=None)

CCSNe implementation of the plot function where you specify the data on the y-axis which is automatically plotted against the mass coordinates on the x-axis. See this function for more details and a description of the optional arguments.

If a single model is shown, then by default the onion shell structure is also drawn if onion=True or if onion=None.

The y-axis is drawn on a logarithmic scale if semilog=True.

Note Weights are calculated using add_weights_ccsne where each weight is multiplied by the mass associated with each mass coordinate in CCSNe models.

Source code in simple/ccsne.py
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
@utils.set_default_kwargs(inherits_=plotting.plot,
                                  linestyle=True, marker=False, fig_size=(10,5),
                                  xhist=False)
def plot_ccsne(models, ykey, *,
         semilog = False, onion=None,
         kwargs=None):
    """
    CCSNe implementation of the [`plot`][simple.plot] function where you specify the data on the y-axis which is
    automatically plotted against the mass coordinates on the x-axis. See this function for more details and a
    description of the optional arguments.

    If a single model is shown, then by default the onion shell structure is also drawn if
    `onion=True` or if `onion=None`.

    The y-axis is drawn on a logarithmic scale if `semilog=True`.

    **Note** Weights are calculated using [`add_weights_ccsne`][simple.ccsne.add_weights_ccsne] where each
    weight is multiplied by the mass associated with each mass coordinate in CCSNe models.
    """

    onion_kwargs = kwargs.pop_many(prefix=['onion', 'zone'])
    if semilog: kwargs.setdefault('ax_yscale', 'log')
    kwargs.setdefault('SIMPLE_add_weights', add_weights_ccsne)

    modeldata, axis_labels = plotting.plot_get_data(models, '.masscoord', ykey,
                                           xunit=None, kwargs=kwargs)

    ax = plotting.plot_draw(modeldata, axis_labels, kwargs=kwargs)

    if onion or (onion is None and len(modeldata) == 1):
        if len(modeldata) > 1:
            raise ValueError(f"Can only plot onion structure for a single model")
        else:
            plot_zonal_structure(list(modeldata.keys())[0], ax=ax, **onion_kwargs)

    return ax

simple.ccsne.plot_zonal_structure

plot_zonal_structure(model, *, ax=None, update_ax=True, update_fig=True, kwargs=None)

Visualise the onion-shell structure for a single CCSNe model.

Source code in simple/ccsne.py
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
@utils.set_default_kwargs(
    # Default settings for line, text and fill
    ax_kw_title_pad = 20,
    default_line_color='black', default_line_linestyle='--', default_line_lw=2, default_line_alpha=0.75,
    default_text_fontsize=10., default_text_color='black',
    default_text_horizontalalignment='center', default_text_xycoords=('data', 'axes fraction'), default_text_y = 1.01,
    default_fill_color='lightblue', default_fill_alpha=0.25,

    # For the rest we only need to give the values that differ from the default
   remnant_line_linestyle=':', remnant_fill_color='gray', remnant_fill_alpha=0.5,
   HeN_fill_show=False,
   OC_fill_show=False,
   OSi_fill_show=False,
   Ni_fill_show=False, )
def plot_zonal_structure(model, *, ax=None, update_ax=True, update_fig=True, kwargs=None):
    """Visualise the onion-shell structure for a single CCSNe model."""
    if not isinstance(model, models.ModelBase):
        raise ValueError(f'model must be an Model object not {type(model)}')

    ax = plotting.get_axes(ax)
    title = ax.get_title()
    if title:
        kwargs.setdefault('ax_title', title)
    plotting.update_axes(ax, kwargs, update_ax=update_ax, update_fig=update_fig)

    masscoord = model.masscoord
    zone = model.zone
    lbound_H = np.flatnonzero(zone == 'H')
    lbound_H = masscoord[lbound_H[0] if lbound_H.size else -1]
    lbound_HeN = np.flatnonzero(zone == 'He/N')
    lbound_HeN = masscoord[lbound_HeN[0] if lbound_HeN.size else -1]
    lbound_HeC = np.flatnonzero(zone == 'He/C')
    lbound_HeC = masscoord[lbound_HeC[0] if lbound_HeC.size else -1]
    lbound_OC = np.flatnonzero(zone == 'O/C')
    lbound_OC = masscoord[lbound_OC[0] if lbound_OC.size else -1]
    lbound_ONe = np.flatnonzero(zone == 'O/Ne')
    lbound_ONe = masscoord[lbound_ONe[0] if lbound_ONe.size else -1]
    lbound_OSi = np.flatnonzero(zone == 'O/Si')
    lbound_OSi = masscoord[lbound_OSi[0] if lbound_OSi.size else -1]
    lbound_Si = np.flatnonzero(zone == 'Si')
    lbound_Si = masscoord[lbound_Si[0] if lbound_Si.size else -1]
    lbound_Ni = np.flatnonzero(zone == 'Ni')
    lbound_Ni = masscoord[lbound_Ni[0] if lbound_Ni.size else -1]

    """
    lbound_H = masscoord[lower_bounds['H'][0]]
    lbound_HeN = masscoord[lower_bounds['He/N'][0]]
    lbound_HeC = masscoord[lower_bounds['He/C'][0]]
    lbound_OC = masscoord[lower_bounds['O/C'][0]]
    lbound_ONe = masscoord[lower_bounds['O/Ne'][0]]
    lbound_OSi = masscoord[lower_bounds['O/Si'][0]]
    lbound_Si = masscoord[lower_bounds['Si'][0]]
    lbound_Ni = masscoord[lower_bounds['Ni'][0]]
    """

    default_line = kwargs.pop_many(prefix='default_line')
    default_text = kwargs.pop_many(prefix='default_text')
    default_fill = kwargs.pop_many(prefix='default_fill')

    def add_line(name, x):
        line_kwargs = kwargs.pop_many(prefix=f'{name}_line', **default_line)
        if line_kwargs.pop('show', True):
            ax.axvline(x, **line_kwargs)

    def add_text(name, text, x):
        text_kwargs = kwargs.pop_many(prefix=f'{name}_text', **default_text)
        if text_kwargs.pop('show', True):
            # Using annotate instead of text as we can then specify x in absolute, and y coordinates relative, in space.
            ax.annotate(text_kwargs.pop('xytext', text), (x, text_kwargs.pop('y', 1.01)),
                        **text_kwargs)

    def add_fill(name, x):
        fill_kwargs = kwargs.pop_many(prefix=f'{name}_fill', **default_fill)
        if fill_kwargs.pop('show', True):
            ax.fill_between(x, fill_kwargs.pop('y1', [ylim[0], ylim[0]]),
                             fill_kwargs.pop('y2', [ylim[1], ylim[1]]),
                            **fill_kwargs)

    def add(name, text, lbound):
        if kwargs.get(f'{name}_show', True) is False:
            return

        if lbound<0 or not (ubound > xlim[0]) or not (lbound < ubound): return ubound

        if not lbound > xlim[0]:
            lbound = xlim[0]
        else:
            add_line(name, lbound)

        add_text(name, text, (lbound + ubound)/2)
        add_fill(name, [lbound, ubound])
        return lbound

    ylim = ax.get_ylim()
    xlim = ax.get_xlim()
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

    # Outside-in since the last lower limit is the upper limit of the next one

    ubound = np.min([model.masscoord[-1], xlim[1]])

    # H - Envelope
    ubound = add('H', 'H', lbound_H)

    # He/N
    ubound = add('HeN', 'He/N', lbound_HeN)

    # He/C
    ubound = add('HeC', 'He/C', lbound_HeC)

    # O/C
    ubound = add('OC', 'O/C',lbound_OC)

    # O/Ne
    ubound = add('ONe', 'O/Ne',lbound_ONe)

    # O/Si
    ubound = add('OSi', 'O/Si', lbound_OSi)

    # Si
    ubound = add('Si', 'Si', lbound_Si)

    # Ni
    ubound = add('Ni', 'Ni', lbound_Ni)

    # Remnant
    masscut = model.masscoord[0]
    if xlim[0] < masscut:
        lbound_rem = np.max([0,xlim[0]])
        add_text('remnant', r'M$_{\rm rem}$', ((lbound_rem + masscut) / 2))
        add_fill('remnant', [lbound_rem, masscut])

    return ax