Пример #1
0
    def convolve_energies(self, events, prng):
        """
        Convolve the events with a RMF file.
        """
        mylog.info("Reading response matrix file (RMF): %s" % self.rmf)
        rmf = RedistributionMatrixFile(self.rmf)

        eidxs = np.argsort(events["eobs"])
        sorted_e = events["eobs"][eidxs].d

        detectedChannels = []

        # run through all photon energies and find which bin they go in
        fcurr = 0
        last = sorted_e.size

        pbar = get_pbar("Scattering energies with RMF", last)

        for (k, low), high in zip(enumerate(rmf.elo), rmf.ehi):
            # weight function for probabilities from RMF
            weights = np.nan_to_num(np.float64(rmf.data["MATRIX"][k]))
            if weights.sum() <= 0.0:
                continue
            weights /= weights.sum()
            # build channel number list associated to array value,
            # there are groups of channels in rmfs with nonzero probabilities
            trueChannel = []
            f_chan = ensure_numpy_array(np.nan_to_num(rmf.data["F_CHAN"][k]))
            n_chan = ensure_numpy_array(np.nan_to_num(rmf.data["N_CHAN"][k]))
            for start, nchan in zip(f_chan, n_chan):
                if start > -1:
                    if nchan == 0:
                        trueChannel.append(start)
                    else:
                        trueChannel += list(range(start, start + nchan))
            trueChannel = np.array(trueChannel)
            nc = trueChannel.size
            if nc > 0:
                ww = weights[:nc]
                e = sorted_e[fcurr:last]
                nn = np.logical_and(low <= e, e < high).sum()
                channelInd = prng.choice(nc, size=nn, p=ww)
                detectedChannels.append(trueChannel[channelInd])
                fcurr += nn
                pbar.update(fcurr)

        pbar.finish()

        for key in ["xpix", "ypix", "xsky", "ysky"]:
            events.events[key] = events[key][eidxs]

        events.events["eobs"] = YTArray(sorted_e, "keV")
        events.events[rmf.header["CHANTYPE"]] = np.concatenate(
            detectedChannels).astype("int")

        events.parameters["RMF"] = rmf.filename
        events.parameters["ChannelType"] = rmf.header["CHANTYPE"]
        events.parameters["Telescope"] = rmf.header["TELESCOP"]
        events.parameters["Instrument"] = rmf.header["INSTRUME"]
        events.parameters["Mission"] = rmf.header.get("MISSION", "")
Пример #2
0
    def write_simput_file(self, prefix, overwrite=False, emin=None, emax=None):
        r"""
        Write events to a SIMPUT file that may be read by the SIMX instrument
        simulator.

        Parameters
        ----------
        prefix : string
            The filename prefix.
        overwrite : boolean, optional
            Set to True to overwrite previous files.
        e_min : float, optional
            The minimum energy of the photons to save in keV.
        e_max : float, optional
            The maximum energy of the photons to save in keV.
        """
        if isinstance(self, ConvolvedEventList):
            raise NotImplementedError(
                "Writing SIMPUT files is only supported if "
                "you didn't convolve with responses!")

        events = communicate_events(self.events)

        if comm.rank == 0:

            mylog.info("Writing SIMPUT catalog file %s_simput.fits " % prefix +
                       "and SIMPUT photon list file %s_phlist.fits." % prefix)

            if emin is None and emax is None:
                idxs = slice(None, None, None)
            else:
                if emin is None:
                    emin = events["eobs"].min().value
                if emax is None:
                    emax = events["eobs"].max().value
                idxs = np.logical_and(events["eobs"].d >= emin,
                                      events["eobs"].d <= emax)

            flux = np.sum(events["eobs"][idxs]).to("erg") / \
                   self.parameters["exp_time"]/self.parameters["area"]

            write_photon_list(prefix,
                              prefix,
                              flux.v,
                              events["xsky"][idxs].d,
                              events["ysky"][idxs].d,
                              events["eobs"][idxs].d,
                              overwrite=overwrite)

        comm.barrier()
Пример #3
0
    def write_simput_file(self, prefix, overwrite=False, emin=None, emax=None):
        r"""
        Write events to a SIMPUT file that may be read by the SIMX instrument
        simulator.

        Parameters
        ----------
        prefix : string
            The filename prefix.
        overwrite : boolean, optional
            Set to True to overwrite previous files.
        e_min : float, optional
            The minimum energy of the photons to save in keV.
        e_max : float, optional
            The maximum energy of the photons to save in keV.
        """
        if isinstance(self, ConvolvedEventList):
            raise NotImplementedError("Writing SIMPUT files is only supported if "
                                      "you didn't convolve with responses!")

        events = communicate_events(self.events)

        if comm.rank == 0:

            mylog.info("Writing SIMPUT catalog file %s_simput.fits " % prefix +
                       "and SIMPUT photon list file %s_phlist.fits." % prefix)

            if emin is None and emax is None:
                idxs = slice(None, None, None)
            else:
                if emin is None:
                    emin = events["eobs"].min().value
                if emax is None:
                    emax = events["eobs"].max().value
                idxs = np.logical_and(events["eobs"].d >= emin, events["eobs"].d <= emax)

            flux = np.sum(events["eobs"][idxs]).to("erg") / \
                   self.parameters["exp_time"]/self.parameters["area"]

            write_photon_list(prefix, prefix, flux.v, events["xsky"][idxs].d,
                              events["ysky"][idxs].d, events["eobs"][idxs].d,
                              overwrite=overwrite)

        comm.barrier()
Пример #4
0
    def _add_events(self, ebins, spectrum, prng, absorb_model):
        exp_time = self.parameters["ExposureTime"]
        area = self.parameters["Area"]
        flux = spectrum.sum()
        num_photons = prng.poisson(lam=exp_time*area*flux)
        cumspec = np.cumsum(spectrum)
        cumspec = np.insert(cumspec, 0, 0.0)
        cumspec /= cumspec[-1]
        randvec = prng.uniform(size=num_photons)
        randvec.sort()
        e = YTArray(np.interp(randvec, cumspec, ebins), "keV")

        if absorb_model is None:
            detected = np.ones(e.shape, dtype='bool')
        else:
            detected = absorb_model.absorb_photons(e, prng=prng)

        mylog.info("Adding %d new events." % detected.sum())

        return e[detected]
Пример #5
0
 def apply_effective_area(self, events, prng):
     """
     Convolve the events with a ARF file.
     """
     mylog.info("Applying energy-dependent effective area.")
     arf = AuxiliaryResponseFile(self.arf, rmffile=self.rmf)
     # If the area which was used to create the events is smaller than
     # the maximum area in the ARF, scream loudly.
     if events.parameters["Area"] < arf.max_area:
         raise RuntimeError(
             "The area used to create the events is less than "
             "the maximum of the effective area curve! Re-create the "
             "events with a collecting area higher than %s!" % arf.max_area)
     detected = arf.detect_events(events["eobs"],
                                  events.parameters["Area"],
                                  prng=prng)
     mylog.info("%s events detected." % detected.sum())
     for key in ["xpix", "ypix", "xsky", "ysky", "eobs"]:
         events.events[key] = events[key][detected]
     events.parameters["ARF"] = arf.filename
     events.num_events = len(events.events["eobs"])
Пример #6
0
    def absorb_photons(self, eobs, prng=np.random):
        r"""
        Determine which photons will be absorbed by foreground
        galactic absorption.

        Parameters
        ----------
        eobs : array_like
            The energies of the photons in keV.
        prng : :class:`~numpy.random.RandomState` object or :mod:`~numpy.random`, optional
            A pseudo-random number generator. Typically will only be specified
            if you have a reason to generate the same set of random numbers, such as for a
            test. Default is the :mod:`numpy.random` module.
        """
        mylog.info("Absorbing.")
        self.prepare_spectrum()
        absorb = self.get_absorb(eobs)
        randvec = prng.uniform(size=eobs.shape)
        detected = randvec < absorb
        self.cleanup_spectrum()
        return detected
Пример #7
0
    def setup_model(self, data_source, redshift, spectral_norm):
        self.redshift = redshift
        ptype = None
        if not isinstance(self.Zmet, float):
            Z_units = str(data_source.ds._get_field_info(self.Zmet).units)
            if Z_units in ["dimensionless", "", "code_metallicity"]:
                self.Zconvert = 1.0 / 0.019
            elif Z_units == "Zsun":
                self.Zconvert = 1.0
            else:
                raise RuntimeError(
                    "I don't understand metallicity units of %s!" % Z_units)
        if self.emission_measure_field is None:
            found_dfield = [
                fd for fd in particle_dens_fields
                if fd in data_source.ds.field_list
            ]
            if len(found_dfield) > 0:
                ptype = found_dfield[0][0]

                def _emission_measure(field, data):
                    nenh = data[found_dfield[0]] * data['particle_mass']
                    nenh /= mp * mp
                    nenh.convert_to_units("cm**-3")
                    if data.has_field_parameter("X_H"):
                        X_H = data.get_field_parameter("X_H")
                    else:
                        X_H = 0.76
                    if (ptype,
                            'ElectronAbundance') in data_source.ds.field_list:
                        nenh *= X_H * data[ptype, 'ElectronAbundance']
                        nenh *= X_H * (1. -
                                       data[ptype, 'NeutralHydrogenAbundance'])
                    else:
                        nenh *= 0.5 * (1. + X_H) * X_H
                    return nenh

                data_source.ds.add_field((ptype, 'emission_measure'),
                                         function=_emission_measure,
                                         particle_type=True,
                                         units="cm**-3")
                self.emission_measure_field = (ptype, 'emission_measure')
            else:
                self.emission_measure_field = ('gas', 'emission_measure')
        mylog.info("Using emission measure field '(%s, %s)'." %
                   self.emission_measure_field)
        if self.temperature_field is None:
            found_tfield = [
                fd for fd in particle_temp_fields
                if fd in data_source.ds.derived_field_list
            ]
            if len(found_tfield) > 0:
                self.temperature_field = found_tfield[0]
                # What we have to do here is make sure that the temperature is set correctly
                # for SPH datasets that don't have the temperature field defined. What this
                # means is that we must set the mean molecular weight to the value for a
                # fully ionized gas if the ionization fraction is not available in the dataset.
                if self.temperature_field not in data_source.ds.field_list and ptype is not None:
                    if (ptype, 'ElectronAbundance'
                        ) not in data_source.ds.field_list:
                        if data_source.has_field_parameter("X_H"):
                            X_H = data_source.get_field_parameter("X_H")
                        else:
                            X_H = 0.76
                        data_source.set_field_parameter(
                            "mean_molecular_weight", 4.0 / (5 * X_H + 3))
            else:
                self.temperature_field = ('gas', 'temperature')
        mylog.info("Using temperature field '(%s, %s)'." %
                   self.temperature_field)
        self.spectral_model.prepare_spectrum(redshift)
        self.spectral_norm = spectral_norm
        if self.kT_scale == "linear":
            self.kT_bins = np.linspace(self.kT_min,
                                       self.kT_max,
                                       num=self.n_kT + 1)
        elif self.kT_scale == "log":
            self.kT_bins = np.logspace(np.log10(self.kT_min),
                                       np.log10(self.kT_max),
                                       num=self.n_kT + 1)
        self.dkT = np.diff(self.kT_bins)
        kT = (kboltz * data_source[self.temperature_field]).in_units("keV").v
        num_cells = np.logical_and(kT > self.kT_min, kT < self.kT_max).sum()
        self.source_type = data_source.ds._get_field_info(
            self.emission_measure_field).name[0]
        self.pbar = get_pbar("Generating photons ", num_cells)
Пример #8
0
def make_xrb_particles(data_source, age_field, scale_length, 
                       sfr_time_range=(1.0, "Gyr"), prng=None):
    r"""
    This routine generates an in-memory dataset composed of X-ray binary particles
    from an input data source containing star particles. 

    Parameters
    ----------
    data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer`
        The yt data source to obtain the data from, such as a sphere, box, disk, 
        etc.
    age_field : string or (type, name) field tuple
        The stellar age field. Must be in some kind of time units. 
    scale_length : string, (ftype, fname) tuple, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
        The radial length scale over which to scatter the XRB particles
        from their parent star particle. Can be the name of a smoothing
        length field for the stars, a (value, unit) tuple, or a YTQuantity.
    sfr_time_range : string, (ftype, fname) tuple, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`, optional
        The recent time range over which to calculate the star formation rate from
        the current time in the dataset. Default: 1.0 Gyr
    prng : integer or :class:`~numpy.random.RandomState` object 
        A pseudo-random number generator. Typically will only be specified
        if you have a reason to generate the same set of random numbers, such as for a
        test. Default is to use the :mod:`numpy.random` module.
    """
    prng = parse_prng(prng)

    ds = data_source.ds

    ptype = data_source._determine_fields(age_field)[0][0]

    t = data_source[age_field].to("Gyr")
    m = data_source[(ptype, "particle_mass")].to("Msun")

    sfr_time_range = parse_value(sfr_time_range, "Gyr")

    recent = t < sfr_time_range

    n_recent = recent.sum()

    if n_recent == 0:
        sfr = 0.0
    else:
        sfr = (m[recent].sum()/sfr_time_range).to("Msun/yr").v

    mylog.info("%d star particles were formed in the last " % n_recent +
               "%s for a SFR of %4.1f Msun/yr." % (sfr_time_range, sfr))

    mtot = m.sum()

    npart = m.size

    scale_field = None
    if isinstance(scale_length, tuple):
        if isinstance(scale_length[0], string_types):
            scale_field = scale_length
    elif isinstance(scale_length, string_types):
        scale_field = (ptype, scale_length)

    if scale_field is None:
        if isinstance(scale_length, tuple):
            scale = YTArray([scale_length[0]]*npart, scale_length[1])
        elif isinstance(scale_length, YTQuantity):
            scale = YTArray([scale_length]*npart)
        else:
            scale = YTArray([scale_length[0]]*npart, "kpc")
    else:
        scale = data_source[scale_length]

    scale = scale.to('kpc').d

    N_l = lmxb_cdf(Lcut)*mtot.v*1.0e-11
    N_h = hmxb_cdf(Lcut)*sfr

    N_all = N_l+N_h

    if N_all == 0.0:
        raise RuntimeError("There are no X-ray binaries to generate!")

    # Compute conversion factors from luminosity to count rate

    lmxb_factor = get_scale_factor(alpha_lmxb, emin_lmxb, emax_lmxb)
    hmxb_factor = get_scale_factor(alpha_hmxb, emin_hmxb, emax_hmxb)

    xp = []
    yp = []
    zp = []
    vxp = []
    vyp = []
    vzp = []
    lp = []
    rp = []
    ap = []

    if N_l > 0.0:

        F_l = np.zeros(nbins+1)
        for i in range(1, nbins+1):
            F_l[i] = lmxb_cdf(Lbins[i]) 
        F_l /= F_l[-1]
        invcdf_l = InterpolatedUnivariateSpline(F_l, logLbins)

        n_l = prng.poisson(lam=N_l*m/mtot)

        mylog.info("Number of low-mass X-ray binaries: %s" % n_l.sum())

        for i, n in enumerate(n_l):
            if n > 0:
                randvec = prng.uniform(size=n)
                l = YTArray(10**invcdf_l(randvec)*1.0e38, "erg/s")
                r = YTArray(l.v*lmxb_factor, "photons/s/keV")
                # Now convert output luminosities to bolometric
                l *= bc_lmxb
                x = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                y = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                z = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                x += data_source[ptype, "particle_position_x"][i].to("kpc")
                y += data_source[ptype, "particle_position_y"][i].to("kpc")
                z += data_source[ptype, "particle_position_z"][i].to("kpc")
                vx = YTArray([data_source[ptype, "particle_velocity_x"][i]]*n).to('km/s')
                vy = YTArray([data_source[ptype, "particle_velocity_y"][i]]*n).to('km/s')
                vz = YTArray([data_source[ptype, "particle_velocity_z"][i]]*n).to('km/s')
                xp.append(x)
                yp.append(y)
                zp.append(z)
                vxp.append(vx)
                vyp.append(vy)
                vzp.append(vz)
                lp.append(l)
                rp.append(r)
                ap.append(np.array([alpha_lmxb]*n))

    if N_h > 0.0:

        F_h = np.zeros(nbins+1)
        for i in range(1, nbins+1):
            F_h[i] = hmxb_cdf(Lbins[i])
        F_h /= F_h[-1]
        invcdf_h = InterpolatedUnivariateSpline(F_h, logLbins)

        n_h = prng.poisson(lam=N_h*m/mtot)

        mylog.info("Number of high-mass X-ray binaries: %s" % n_h.sum())

        for i, n in enumerate(n_h):
            if n > 0:
                randvec = prng.uniform(size=n)
                l = YTArray(10**invcdf_h(randvec)*1.0e38, "erg/s")
                r = YTArray(l.v*hmxb_factor, "photons/s/keV")
                # Now convert output luminosities to bolometric
                l *= bc_hmxb
                x = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                y = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                z = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                x += data_source[ptype, "particle_position_x"][i].to("kpc")
                y += data_source[ptype, "particle_position_y"][i].to("kpc")
                z += data_source[ptype, "particle_position_z"][i].to("kpc")
                vx = YTArray([data_source[ptype, "particle_velocity_x"][i]]*n).to('km/s')
                vy = YTArray([data_source[ptype, "particle_velocity_y"][i]]*n).to('km/s')
                vz = YTArray([data_source[ptype, "particle_velocity_z"][i]]*n).to('km/s')
                xp.append(x)
                yp.append(y)
                zp.append(z)
                vxp.append(vx)
                vyp.append(vy)
                vzp.append(vz)
                lp.append(l)
                rp.append(r)
                ap.append(np.array([alpha_hmxb]*n))

    xp = uconcatenate(xp)
    yp = uconcatenate(yp)
    zp = uconcatenate(zp)
    vxp = uconcatenate(vxp)
    vyp = uconcatenate(vyp)
    vzp = uconcatenate(vzp)
    lp = uconcatenate(lp)
    rp = uconcatenate(rp)
    ap = uconcatenate(ap)

    data = {"particle_position_x": (xp.d, str(xp.units)),
            "particle_position_y": (yp.d, str(yp.units)),
            "particle_position_z": (zp.d, str(zp.units)),
            "particle_velocity_x": (vxp.d, str(vxp.units)),
            "particle_velocity_y": (vyp.d, str(vyp.units)),
            "particle_velocity_z": (vzp.d, str(vzp.units)),
            "particle_luminosity": (lp.d, str(lp.units)),
            "particle_count_rate": (rp.d, str(rp.units)),
            "particle_spectral_index": ap}

    dle = ds.domain_left_edge.to("kpc").v
    dre = ds.domain_right_edge.to("kpc").v

    bbox = np.array([[dle[i], dre[i]] for i in range(3)])

    new_ds = load_particles(data, bbox=bbox, length_unit="kpc",
                            time_unit="Myr", mass_unit="Msun", 
                            velocity_unit="km/s")

    return new_ds
Пример #9
0
 def setup_model(self, data_source, redshift, spectral_norm):
     self.redshift = redshift
     ptype = None
     if not self.nei and not isinstance(self.Zmet, float):
         Z_units = str(data_source.ds._get_field_info(self.Zmet).units)
         if Z_units in ["dimensionless", "", "code_metallicity"]:
             self.Zconvert = 1.0/metal_abund[self.abund_table]
         elif Z_units == "Zsun":
             self.Zconvert = 1.0
         else:
             raise RuntimeError("I don't understand metallicity units of %s!" % Z_units)
     if self.num_var_elem > 0:
         for key, value in self.var_elem.items():
             if not isinstance(value, float):
                 if "^" in key:
                     elem = key.split("^")[0]
                 else:
                     elem = key
                 n_elem = elem_names.index(elem)
                 m_units = str(data_source.ds._get_field_info(value).units)
                 if m_units in ["dimensionless", "", "code_metallicity"]:
                     self.mconvert[key] = atomic_weights[1]/(self.atable[n_elem] *
                                                             atomic_weights[n_elem] *
                                                             solar_H_abund)
                 elif m_units == "Zsun":
                     self.mconvert[key] = 1.0
                 else:
                     raise RuntimeError("I don't understand units of %s for element %s!" % (m_units, key))
     if self.emission_measure_field is None:
         found_dfield = [fd for fd in particle_dens_fields if fd in data_source.ds.field_list]
         if len(found_dfield) > 0:
             ptype = found_dfield[0][0]
             def _emission_measure(field, data):
                 nenh = data[found_dfield[0]]*data['particle_mass']
                 nenh /= mp*mp
                 nenh.convert_to_units("cm**-3")
                 if data.has_field_parameter("X_H"):
                     X_H = data.get_field_parameter("X_H")
                 else:
                     X_H = primordial_H_abund
                 if (ptype, 'ElectronAbundance') in data_source.ds.field_list:
                     nenh *= X_H * data[ptype, 'ElectronAbundance']
                     nenh *= X_H * (1.-data[ptype, 'NeutralHydrogenAbundance'])
                 else:
                     nenh *= 0.5*(1.+X_H)*X_H
                 return nenh
             data_source.ds.add_field((ptype, 'emission_measure'),
                                      function=_emission_measure,
                                      particle_type=True,
                                      units="cm**-3")
             self.emission_measure_field = (ptype, 'emission_measure')
         else:
             self.emission_measure_field = ('gas', 'emission_measure')
     mylog.info("Using emission measure field '(%s, %s)'." % self.emission_measure_field)
     if self.temperature_field is None:
         found_tfield = [fd for fd in particle_temp_fields if fd in data_source.ds.derived_field_list]
         if len(found_tfield) > 0:
             self.temperature_field = found_tfield[0]
             # What we have to do here is make sure that the temperature is set correctly
             # for SPH datasets that don't have the temperature field defined. What this
             # means is that we must set the mean molecular weight to the value for a
             # fully ionized gas if the ionization fraction is not available in the dataset.
             if self.temperature_field not in data_source.ds.field_list and ptype is not None:
                 if (ptype, 'ElectronAbundance') not in data_source.ds.field_list:
                     if data_source.has_field_parameter("X_H"):
                         X_H = data_source.get_field_parameter("X_H")
                     else:
                         X_H = 0.76
                     data_source.set_field_parameter("mean_molecular_weight", 4.0/(5*X_H+3))
         else:
             self.temperature_field = ('gas', 'temperature')
     mylog.info("Using temperature field '(%s, %s)'." % self.temperature_field)
     self.spectral_model.prepare_spectrum(redshift)
     self.spectral_norm = spectral_norm
     if self.kT_scale == "linear":
         self.kT_bins = np.linspace(self.kT_min, self.kT_max, num=self.n_kT+1)
     elif self.kT_scale == "log":
         self.kT_bins = np.logspace(np.log10(self.kT_min), np.log10(self.kT_max), 
                                    num=self.n_kT+1)
     self.dkT = np.diff(self.kT_bins)
     citer = data_source.chunks([], "io")
     num_cells = 0
     T_min = self.kT_min*K_per_keV
     T_max = self.kT_max*K_per_keV
     for chunk in parallel_objects(citer):
         T = chunk[self.temperature_field].d
         num_cells += np.count_nonzero((T > T_min) & (T < T_max))
     num_cells = comm.mpi_allreduce(num_cells)
     self.source_type = data_source.ds._get_field_info(self.emission_measure_field).name[0]
     self.pbar = get_pbar("Processing cells/particles ", num_cells)
Пример #10
0
    def project_photons(self,
                        normal,
                        sky_center,
                        absorb_model=None,
                        nH=None,
                        no_shifting=False,
                        north_vector=None,
                        sigma_pos=None,
                        kernel="top_hat",
                        prng=None,
                        **kwargs):
        r"""
        Projects photons onto an image plane given a line of sight.
        Returns a new :class:`~pyxsim.event_list.EventList`.

        Parameters
        ----------
        normal : character or array-like
            Normal vector to the plane of projection. If "x", "y", or "z", will
            assume to be along that axis (and will probably be faster). Otherwise,
            should be an off-axis normal vector, e.g [1.0, 2.0, -3.0]
        sky_center : array-like
            Center RA, Dec of the events in degrees.
        absorb_model : string or :class:`~pyxsim.spectral_models.AbsorptionModel`
            A model for foreground galactic absorption, to simulate the
            absorption of events before being detected. This cannot be applied
            here if you already did this step previously in the creation of the
            :class:`~pyxsim.photon_list.PhotonList` instance. Known options for
            strings are "wabs" and "tbabs".
        nH : float, optional
            The foreground column density in units of 10^22 cm^{-2}. Only used
            if absorption is applied.
        no_shifting : boolean, optional
            If set, the photon energies will not be Doppler shifted.
        north_vector : a sequence of floats
            A vector defining the "up" direction. This option sets the
            orientation of the plane of projection. If not set, an arbitrary
            grid-aligned north_vector is chosen. Ignored in the case where a
            particular axis (e.g., "x", "y", or "z") is explicitly specified.
        sigma_pos : float, optional
            Apply a gaussian smoothing operation to the sky positions of the
            events. This may be useful when the binned events appear blocky due
            to their uniform distribution within simulation cells. However, this
            will move the events away from their originating position on the
            sky, and so may distort surface brightness profiles and/or spectra.
            Should probably only be used for visualization purposes. Supply a
            float here to smooth with a standard deviation with this fraction
            of the cell size. Default: None
        kernel : string, optional
            The kernel used when smoothing positions of X-rays originating from
            SPH particles, "gaussian" or "top_hat". Default: "top_hat".
        prng : integer or :class:`~numpy.random.RandomState` object 
            A pseudo-random number generator. Typically will only be specified
            if you have a reason to generate the same set of random numbers,
            such as for a test. Default is to use the :mod:`numpy.random`
            module.

        Examples
        --------
        >>> L = np.array([0.1,-0.2,0.3])
        >>> events = my_photons.project_photons(L, [30., 45.])
        """
        prng = parse_prng(prng)

        scale_shift = -1.0 / clight.to("km/s")

        if "smooth_positions" in kwargs:
            issue_deprecation_warning(
                "'smooth_positions' has been renamed to "
                "'sigma_pos' and the former is deprecated!")
            sigma_pos = kwargs["smooth_positions"]

        if "redshift_new" in kwargs or "area_new" in kwargs or \
            "exp_time_new" in kwargs or "dist_new" in kwargs:
            issue_deprecation_warning(
                "Changing the redshift, distance, area, or "
                "exposure time has been deprecated in "
                "project_photons!")

        if sigma_pos is not None and self.parameters[
                "data_type"] == "particles":
            raise RuntimeError(
                "The 'smooth_positions' argument should not be used with "
                "particle-based datasets!")

        if isinstance(absorb_model, string_types):
            if absorb_model not in absorb_models:
                raise KeyError("%s is not a known absorption model!" %
                               absorb_model)
            absorb_model = absorb_models[absorb_model]
        if absorb_model is not None:
            if nH is None:
                raise RuntimeError(
                    "You specified an absorption model, but didn't "
                    "specify a value for nH!")
            absorb_model = absorb_model(nH)

        sky_center = YTArray(sky_center, "degree")

        n_ph = self.photons["num_photons"]

        if not isinstance(normal, string_types):
            L = np.array(normal)
            orient = Orientation(L, north_vector=north_vector)
            x_hat = orient.unit_vectors[0]
            y_hat = orient.unit_vectors[1]
            z_hat = orient.unit_vectors[2]
        else:
            x_hat = np.zeros(3)
            y_hat = np.zeros(3)
            z_hat = np.zeros(3)

        parameters = {}

        D_A = self.parameters["fid_d_a"]

        events = {}

        eobs = self.photons["energy"].v

        if not no_shifting:
            if comm.rank == 0:
                mylog.info("Doppler-shifting photon energies.")
            if isinstance(normal, string_types):
                shift = self.photons["vel"][:,
                                            "xyz".index(normal)] * scale_shift
            else:
                shift = np.dot(self.photons["vel"], z_hat) * scale_shift
            doppler_shift(shift, n_ph, eobs)

        if absorb_model is None:
            det = np.ones(eobs.size, dtype='bool')
            num_det = eobs.size
        else:
            if comm.rank == 0:
                mylog.info("Foreground galactic absorption: using "
                           "the %s model and nH = %g." %
                           (absorb_model._name, nH))
            det = absorb_model.absorb_photons(eobs, prng=prng)
            num_det = det.sum()

        events["eobs"] = YTArray(eobs[det], "keV")

        num_events = comm.mpi_allreduce(num_det)

        if comm.rank == 0:
            mylog.info("%d events have been detected." % num_events)

        if num_det > 0:

            if comm.rank == 0:
                mylog.info("Assigning positions to events.")

            if isinstance(normal, string_types):
                norm = "xyz".index(normal)
            else:
                norm = normal

            xsky, ysky = scatter_events(norm, prng, kernel,
                                        self.parameters["data_type"], num_det,
                                        det, self.photons["num_photons"],
                                        self.photons["pos"].d,
                                        self.photons["dx"].d, x_hat, y_hat)

            if self.parameters[
                    "data_type"] == "cells" and sigma_pos is not None:
                if comm.rank == 0:
                    mylog.info("Optionally smoothing sky positions.")
                sigma = sigma_pos * np.repeat(self.photons["dx"].d, n_ph)[det]
                xsky += sigma * prng.normal(loc=0.0, scale=1.0, size=num_det)
                ysky += sigma * prng.normal(loc=0.0, scale=1.0, size=num_det)

            d_a = D_A.to("kpc").v
            xsky /= d_a
            ysky /= d_a

            if comm.rank == 0:
                mylog.info("Converting pixel to sky coordinates.")

            pixel_to_cel(xsky, ysky, sky_center)

        else:

            xsky = []
            ysky = []

        events["xsky"] = YTArray(xsky, "degree")
        events["ysky"] = YTArray(ysky, "degree")

        parameters["exp_time"] = self.parameters["fid_exp_time"]
        parameters["area"] = self.parameters["fid_area"]
        parameters["sky_center"] = sky_center

        return EventList(events, parameters)
Пример #11
0
    def from_data_source(cls,
                         data_source,
                         redshift,
                         area,
                         exp_time,
                         source_model,
                         point_sources=False,
                         parameters=None,
                         center=None,
                         dist=None,
                         cosmology=None,
                         velocity_fields=None):
        r"""
        Initialize a :class:`~pyxsim.photon_list.PhotonList` from a yt data
        source. The redshift, collecting area, exposure time, and cosmology
        are stored in the *parameters* dictionary which is passed to the
        *source_model* function.

        Parameters
        ----------
        data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer`
            The data source from which the photons will be generated.
        redshift : float
            The cosmological redshift for the photons.
        area : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The collecting area to determine the number of photons. If units are
            not specified, it is assumed to be in cm^2.
        exp_time : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The exposure time to determine the number of photons. If units are
            not specified, it is assumed to be in seconds.
        source_model : :class:`~pyxsim.source_models.SourceModel`
            A source model used to generate the photons.
        point_sources : boolean, optional
            If True, the photons will be assumed to be generated from the exact
            positions of the cells or particles and not smeared around within
            a volume. Default: False
        parameters : dict, optional
            A dictionary of parameters to be passed for the source model to use,
            if necessary.
        center : string or array_like, optional
            The origin of the photon spatial coordinates. Accepts "c", "max", or
            a coordinate. If not specified, pyxsim attempts to use the "center"
            field parameter of the data_source.
        dist : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The angular diameter distance, used for nearby sources. This may be
            optionally supplied instead of it being determined from the
            *redshift* and given *cosmology*. If units are not specified, it is
            assumed to be in kpc. To use this, the redshift must be set to zero.
        cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional
            Cosmological information. If not supplied, we try to get the
            cosmology from the dataset. Otherwise, LCDM with the default yt 
            parameters is assumed.
        velocity_fields : list of fields
            The yt fields to use for the velocity. If not specified, the 
            following will be assumed:
            ['velocity_x', 'velocity_y', 'velocity_z'] for grid datasets
            ['particle_velocity_x', 'particle_velocity_y', 'particle_velocity_z'] for particle datasets

        Examples
        --------
        >>> thermal_model = ThermalSourceModel(apec_model, Zmet=0.3)
        >>> redshift = 0.05
        >>> area = 6000.0 # assumed here in cm**2
        >>> time = 2.0e5 # assumed here in seconds
        >>> sp = ds.sphere("c", (500., "kpc"))
        >>> my_photons = PhotonList.from_data_source(sp, redshift, area,
        ...                                          time, thermal_model)
        """
        ds = data_source.ds

        if parameters is None:
            parameters = {}
        if cosmology is None:
            if hasattr(ds, 'cosmology'):
                cosmo = ds.cosmology
            else:
                cosmo = Cosmology()
        else:
            cosmo = cosmology
        if dist is None:
            if redshift <= 0.0:
                msg = "If redshift <= 0.0, you must specify a distance to the " \
                      "source using the 'dist' argument!"
                mylog.error(msg)
                raise ValueError(msg)
            D_A = cosmo.angular_diameter_distance(0.0,
                                                  redshift).in_units("Mpc")
        else:
            D_A = parse_value(dist, "kpc")
            if redshift > 0.0:
                mylog.warning("Redshift must be zero for nearby sources. "
                              "Resetting redshift to 0.0.")
                redshift = 0.0

        if isinstance(center, string_types):
            if center == "center" or center == "c":
                parameters["center"] = ds.domain_center
            elif center == "max" or center == "m":
                parameters["center"] = ds.find_max("density")[-1]
        elif iterable(center):
            if isinstance(center, YTArray):
                parameters["center"] = center.in_units("code_length")
            elif isinstance(center, tuple):
                if center[0] == "min":
                    parameters["center"] = ds.find_min(center[1])[-1]
                elif center[0] == "max":
                    parameters["center"] = ds.find_max(center[1])[-1]
                else:
                    raise RuntimeError
            else:
                parameters["center"] = ds.arr(center, "code_length")
        elif center is None:
            if hasattr(data_source, "left_edge"):
                parameters["center"] = 0.5 * (data_source.left_edge +
                                              data_source.right_edge)
            else:
                parameters["center"] = data_source.get_field_parameter(
                    "center")

        parameters["fid_exp_time"] = parse_value(exp_time, "s")
        parameters["fid_area"] = parse_value(area, "cm**2")
        parameters["fid_redshift"] = redshift
        parameters["fid_d_a"] = D_A
        parameters["hubble"] = cosmo.hubble_constant
        parameters["omega_matter"] = cosmo.omega_matter
        parameters["omega_lambda"] = cosmo.omega_lambda

        if redshift > 0.0:
            mylog.info(
                "Cosmology: h = %g, omega_matter = %g, omega_lambda = %g" %
                (cosmo.hubble_constant, cosmo.omega_matter,
                 cosmo.omega_lambda))
        else:
            mylog.info("Observing local source at distance %s." % D_A)

        D_A = parameters["fid_d_a"].in_cgs()
        dist_fac = 1.0 / (4. * np.pi * D_A.value * D_A.value *
                          (1. + redshift)**2)
        spectral_norm = parameters["fid_area"].v * parameters[
            "fid_exp_time"].v * dist_fac

        source_model.setup_model(data_source, redshift, spectral_norm)

        p_fields, v_fields, w_field = determine_fields(
            ds, source_model.source_type, point_sources)

        if velocity_fields is not None:
            v_fields = velocity_fields

        if p_fields[0] == ("index", "x"):
            parameters["data_type"] = "cells"
        else:
            parameters["data_type"] = "particles"

        citer = data_source.chunks([], "io")

        photons = defaultdict(list)

        for chunk in parallel_objects(citer):

            chunk_data = source_model(chunk)

            if chunk_data is not None:
                ncells, number_of_photons, idxs, energies = chunk_data
                photons["num_photons"].append(number_of_photons)
                photons["energy"].append(energies)
                photons["pos"].append(
                    np.array([
                        chunk[p_fields[0]].d[idxs], chunk[p_fields[1]].d[idxs],
                        chunk[p_fields[2]].d[idxs]
                    ]))
                photons["vel"].append(
                    np.array([
                        chunk[v_fields[0]].d[idxs], chunk[v_fields[1]].d[idxs],
                        chunk[v_fields[2]].d[idxs]
                    ]))
                if w_field is None:
                    photons["dx"].append(np.zeros(ncells))
                else:
                    photons["dx"].append(chunk[w_field].d[idxs])

        source_model.cleanup_model()

        photon_units = {
            "pos": ds.field_info[p_fields[0]].units,
            "vel": ds.field_info[v_fields[0]].units,
            "energy": "keV"
        }
        if w_field is None:
            photon_units["dx"] = "kpc"
        else:
            photon_units["dx"] = ds.field_info[w_field].units

        concatenate_photons(ds, photons, photon_units)

        c = parameters["center"].to("kpc")

        if sum(ds.periodicity) > 0:
            # Fix photon coordinates for regions crossing a periodic boundary
            dw = ds.domain_width.to("kpc")
            le, re = find_object_bounds(data_source)
            for i in range(3):
                if ds.periodicity[i] and photons["pos"].shape[0] > 0:
                    tfl = photons["pos"][:, i] < le[i]
                    tfr = photons["pos"][:, i] > re[i]
                    photons["pos"][tfl, i] += dw[i]
                    photons["pos"][tfr, i] -= dw[i]

        # Re-center all coordinates
        if photons["pos"].shape[0] > 0:
            photons["pos"] -= c

        mylog.info("Finished generating photons.")
        mylog.info("Number of photons generated: %d" %
                   int(np.sum(photons["num_photons"])))
        mylog.info("Number of cells with photons: %d" % photons["dx"].size)

        return cls(photons, parameters, cosmo)
Пример #12
0
    def from_file(cls, filename):
        r"""
        Initialize a :class:`~pyxsim.photon_list.PhotonList` from 
        the HDF5 file *filename*.
        """

        mylog.info("Reading photons from %s." % filename)

        photons = {}
        parameters = {}

        f = h5py.File(filename, "r")

        p = f["/parameters"]
        parameters["fid_exp_time"] = YTQuantity(p["fid_exp_time"].value, "s")
        parameters["fid_area"] = YTQuantity(p["fid_area"].value, "cm**2")
        parameters["fid_redshift"] = p["fid_redshift"].value
        parameters["fid_d_a"] = YTQuantity(p["fid_d_a"].value, "Mpc")
        parameters["hubble"] = p["hubble"].value
        parameters["omega_matter"] = p["omega_matter"].value
        parameters["omega_lambda"] = p["omega_lambda"].value
        if "data_type" in p:
            parameters["data_type"] = force_unicode(p["data_type"].value)
        else:
            parameters["data_type"] = "cells"

        d = f["/data"]

        num_cells = d["x"].size
        start_c = comm.rank * num_cells // comm.size
        end_c = (comm.rank + 1) * num_cells // comm.size

        photons["pos"] = YTArray(np.zeros((num_cells, 3)), "kpc")
        photons["vel"] = YTArray(np.zeros((num_cells, 3)), "km/s")
        photons["pos"][:, 0] = d["x"][start_c:end_c]
        photons["pos"][:, 1] = d["y"][start_c:end_c]
        photons["pos"][:, 2] = d["z"][start_c:end_c]
        photons["vel"][:, 0] = d["vx"][start_c:end_c]
        photons["vel"][:, 1] = d["vy"][start_c:end_c]
        photons["vel"][:, 2] = d["vz"][start_c:end_c]
        photons["dx"] = YTArray(d["dx"][start_c:end_c], "kpc")

        n_ph = d["num_photons"][:]

        if comm.rank == 0:
            start_e = np.int64(0)
        else:
            start_e = n_ph[:start_c].sum()
        end_e = start_e + np.int64(n_ph[start_c:end_c].sum())

        photons["num_photons"] = n_ph[start_c:end_c]
        photons["energy"] = YTArray(d["energy"][start_e:end_e], "keV")

        f.close()

        cosmo = Cosmology(hubble_constant=parameters["hubble"],
                          omega_matter=parameters["omega_matter"],
                          omega_lambda=parameters["omega_lambda"])

        mylog.info("Read %d photons from %d %s." %
                   (n_ph.sum(), num_cells, parameters["data_type"]))

        return cls(photons, parameters, cosmo)
Пример #13
0
    def from_data_source(cls, data_source, redshift, area,
                         exp_time, source_model, point_sources=False,
                         parameters=None, center=None, dist=None, 
                         cosmology=None, velocity_fields=None):
        r"""
        Initialize a :class:`~pyxsim.photon_list.PhotonList` from a yt data
        source. The redshift, collecting area, exposure time, and cosmology
        are stored in the *parameters* dictionary which is passed to the
        *source_model* function.

        Parameters
        ----------
        data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer`
            The data source from which the photons will be generated.
        redshift : float
            The cosmological redshift for the photons.
        area : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The collecting area to determine the number of photons. If units are
            not specified, it is assumed to be in cm^2.
        exp_time : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The exposure time to determine the number of photons. If units are
            not specified, it is assumed to be in seconds.
        source_model : :class:`~pyxsim.source_models.SourceModel`
            A source model used to generate the photons.
        point_sources : boolean, optional
            If True, the photons will be assumed to be generated from the exact
            positions of the cells or particles and not smeared around within
            a volume. Default: False
        parameters : dict, optional
            A dictionary of parameters to be passed for the source model to use,
            if necessary.
        center : string or array_like, optional
            The origin of the photon spatial coordinates. Accepts "c", "max", or
            a coordinate. If not specified, pyxsim attempts to use the "center"
            field parameter of the data_source.
        dist : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The angular diameter distance, used for nearby sources. This may be
            optionally supplied instead of it being determined from the
            *redshift* and given *cosmology*. If units are not specified, it is
            assumed to be in kpc. To use this, the redshift must be set to zero.
        cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional
            Cosmological information. If not supplied, we try to get the
            cosmology from the dataset. Otherwise, LCDM with the default yt 
            parameters is assumed.
        velocity_fields : list of fields
            The yt fields to use for the velocity. If not specified, the 
            following will be assumed:
            ['velocity_x', 'velocity_y', 'velocity_z'] for grid datasets
            ['particle_velocity_x', 'particle_velocity_y', 'particle_velocity_z'] for particle datasets

        Examples
        --------
        >>> thermal_model = ThermalSourceModel(apec_model, Zmet=0.3)
        >>> redshift = 0.05
        >>> area = 6000.0 # assumed here in cm**2
        >>> time = 2.0e5 # assumed here in seconds
        >>> sp = ds.sphere("c", (500., "kpc"))
        >>> my_photons = PhotonList.from_data_source(sp, redshift, area,
        ...                                          time, thermal_model)
        """
        ds = data_source.ds

        if parameters is None:
            parameters = {}
        if cosmology is None:
            if hasattr(ds, 'cosmology'):
                cosmo = ds.cosmology
            else:
                cosmo = Cosmology()
        else:
            cosmo = cosmology
        if dist is None:
            if redshift <= 0.0:
                msg = "If redshift <= 0.0, you must specify a distance to the " \
                      "source using the 'dist' argument!"
                mylog.error(msg)
                raise ValueError(msg)
            D_A = cosmo.angular_diameter_distance(0.0, redshift).in_units("Mpc")
        else:
            D_A = parse_value(dist, "kpc")
            if redshift > 0.0:
                mylog.warning("Redshift must be zero for nearby sources. "
                              "Resetting redshift to 0.0.")
                redshift = 0.0

        if isinstance(center, string_types):
            if center == "center" or center == "c":
                parameters["center"] = ds.domain_center
            elif center == "max" or center == "m":
                parameters["center"] = ds.find_max("density")[-1]
        elif iterable(center):
            if isinstance(center, YTArray):
                parameters["center"] = center.in_units("code_length")
            elif isinstance(center, tuple):
                if center[0] == "min":
                    parameters["center"] = ds.find_min(center[1])[-1]
                elif center[0] == "max":
                    parameters["center"] = ds.find_max(center[1])[-1]
                else:
                    raise RuntimeError
            else:
                parameters["center"] = ds.arr(center, "code_length")
        elif center is None:
            if hasattr(data_source, "left_edge"):
                parameters["center"] = 0.5*(data_source.left_edge+data_source.right_edge)
            else:
                parameters["center"] = data_source.get_field_parameter("center")

        parameters["fid_exp_time"] = parse_value(exp_time, "s")
        parameters["fid_area"] = parse_value(area, "cm**2")
        parameters["fid_redshift"] = redshift
        parameters["fid_d_a"] = D_A
        parameters["hubble"] = cosmo.hubble_constant
        parameters["omega_matter"] = cosmo.omega_matter
        parameters["omega_lambda"] = cosmo.omega_lambda

        if redshift > 0.0:
            mylog.info("Cosmology: h = %g, omega_matter = %g, omega_lambda = %g" %
                       (cosmo.hubble_constant, cosmo.omega_matter, cosmo.omega_lambda))
        else:
            mylog.info("Observing local source at distance %s." % D_A)

        D_A = parameters["fid_d_a"].in_cgs()
        dist_fac = 1.0/(4.*np.pi*D_A.value*D_A.value*(1.+redshift)**2)
        spectral_norm = parameters["fid_area"].v*parameters["fid_exp_time"].v*dist_fac

        source_model.setup_model(data_source, redshift, spectral_norm)

        p_fields, v_fields, w_field = determine_fields(ds,
                                                       source_model.source_type,
                                                       point_sources)

        if velocity_fields is not None:
            v_fields = velocity_fields

        if p_fields[0] == ("index", "x"):
            parameters["data_type"] = "cells"
        else:
            parameters["data_type"] = "particles"

        citer = data_source.chunks([], "io")

        photons = defaultdict(list)

        for chunk in parallel_objects(citer):

            chunk_data = source_model(chunk)

            if chunk_data is not None:
                ncells, number_of_photons, idxs, energies = chunk_data
                photons["num_photons"].append(number_of_photons)
                photons["energy"].append(energies)
                photons["pos"].append(np.array([chunk[p_fields[0]].d[idxs],
                                                chunk[p_fields[1]].d[idxs],
                                                chunk[p_fields[2]].d[idxs]]))
                photons["vel"].append(np.array([chunk[v_fields[0]].d[idxs],
                                                chunk[v_fields[1]].d[idxs],
                                                chunk[v_fields[2]].d[idxs]]))
                if w_field is None:
                    photons["dx"].append(np.zeros(ncells))
                else:
                    photons["dx"].append(chunk[w_field].d[idxs])

        source_model.cleanup_model()

        photon_units = {"pos": ds.field_info[p_fields[0]].units,
                        "vel": ds.field_info[v_fields[0]].units,
                        "energy": "keV"}
        if w_field is None:
            photon_units["dx"] = "kpc"
        else:
            photon_units["dx"] = ds.field_info[w_field].units

        concatenate_photons(ds, photons, photon_units)

        c = parameters["center"].to("kpc")

        if sum(ds.periodicity) > 0:
            # Fix photon coordinates for regions crossing a periodic boundary
            dw = ds.domain_width.to("kpc")
            le, re = find_object_bounds(data_source)
            for i in range(3):
                if ds.periodicity[i] and photons["pos"].shape[0] > 0:
                    tfl = photons["pos"][:,i] < le[i]
                    tfr = photons["pos"][:,i] > re[i]
                    photons["pos"][tfl,i] += dw[i]
                    photons["pos"][tfr,i] -= dw[i]

        # Re-center all coordinates
        if photons["pos"].shape[0] > 0:
            photons["pos"] -= c

        mylog.info("Finished generating photons.")
        mylog.info("Number of photons generated: %d" % int(np.sum(photons["num_photons"])))
        mylog.info("Number of cells with photons: %d" % photons["dx"].size)

        return cls(photons, parameters, cosmo)
Пример #14
0
    def from_data_source(cls,
                         data_source,
                         redshift,
                         area,
                         exp_time,
                         source_model,
                         parameters=None,
                         center=None,
                         dist=None,
                         cosmology=None,
                         velocity_fields=None):
        r"""
        Initialize a :class:`~pyxsim.photon_list.PhotonList` from a yt data source.
        The redshift, collecting area, exposure time, and cosmology are stored in the
        *parameters* dictionary which is passed to the *source_model* function.

        Parameters
        ----------
        data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer`
            The data source from which the photons will be generated.
        redshift : float
            The cosmological redshift for the photons.
        area : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`.
            The collecting area to determine the number of photons. If units are
            not specified, it is assumed to be in cm^2.
        exp_time : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`.
            The exposure time to determine the number of photons. If units are
            not specified, it is assumed to be in seconds.
        source_model : :class:`~pyxsim.source_models.SourceModel`
            A source model used to generate the photons.
        parameters : dict, optional
            A dictionary of parameters to be passed for the source model to use, if necessary.
        center : string or array_like, optional
            The origin of the photon spatial coordinates. Accepts "c", "max", or a coordinate.
            If not specified, pyxsim attempts to use the "center" field parameter of the data_source.
        dist : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            The angular diameter distance, used for nearby sources. This may be
            optionally supplied instead of it being determined from the *redshift*
            and given *cosmology*. If units are not specified, it is assumed to be
            in Mpc. To use this, the redshift must be set to zero.
        cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional
            Cosmological information. If not supplied, we try to get
            the cosmology from the dataset. Otherwise, LCDM with
            the default yt parameters is assumed.
        velocity_fields : list of fields
            The yt fields to use for the velocity. If not specified, the following will
            be assumed:
            ['velocity_x', 'velocity_y', 'velocity_z'] for grid datasets
            ['particle_velocity_x', 'particle_velocity_y', 'particle_velocity_z'] for particle datasets

        Examples
        --------
        >>> thermal_model = ThermalSourceModel(apec_model, Zmet=0.3)
        >>> redshift = 0.05
        >>> area = 6000.0 # assumed here in cm**2
        >>> time = 2.0e5 # assumed here in seconds
        >>> sp = ds.sphere("c", (500., "kpc"))
        >>> my_photons = PhotonList.from_data_source(sp, redshift, area,
        ...                                          time, thermal_model)
        """
        ds = data_source.ds

        if parameters is None:
            parameters = {}
        if cosmology is None:
            if hasattr(ds, 'cosmology'):
                cosmo = ds.cosmology
            else:
                cosmo = Cosmology()
        else:
            cosmo = cosmology
        mylog.info(
            "Cosmology: h = %g, omega_matter = %g, omega_lambda = %g" %
            (cosmo.hubble_constant, cosmo.omega_matter, cosmo.omega_lambda))
        if dist is None:
            if redshift <= 0.0:
                msg = "If redshift <= 0.0, you must specify a distance to the source using the 'dist' argument!"
                mylog.error(msg)
                raise ValueError(msg)
            D_A = cosmo.angular_diameter_distance(0.0,
                                                  redshift).in_units("Mpc")
        else:
            D_A = parse_value(dist, "Mpc")
            if redshift > 0.0:
                mylog.warning(
                    "Redshift must be zero for nearby sources. Resetting redshift to 0.0."
                )
                redshift = 0.0

        if center == "center" or center == "c":
            parameters["center"] = ds.domain_center
        elif center == "max" or center == "m":
            parameters["center"] = ds.find_max("density")[-1]
        elif iterable(center):
            if isinstance(center, YTArray):
                parameters["center"] = center.in_units("code_length")
            elif isinstance(center, tuple):
                if center[0] == "min":
                    parameters["center"] = ds.find_min(center[1])[-1]
                elif center[0] == "max":
                    parameters["center"] = ds.find_max(center[1])[-1]
                else:
                    raise RuntimeError
            else:
                parameters["center"] = ds.arr(center, "code_length")
        elif center is None:
            parameters["center"] = data_source.get_field_parameter("center")

        parameters["FiducialExposureTime"] = parse_value(exp_time, "s")
        parameters["FiducialArea"] = parse_value(area, "cm**2")
        parameters["FiducialRedshift"] = redshift
        parameters["FiducialAngularDiameterDistance"] = D_A
        parameters["HubbleConstant"] = cosmo.hubble_constant
        parameters["OmegaMatter"] = cosmo.omega_matter
        parameters["OmegaLambda"] = cosmo.omega_lambda

        D_A = parameters["FiducialAngularDiameterDistance"].in_cgs()
        dist_fac = 1.0 / (4. * np.pi * D_A.value * D_A.value *
                          (1. + redshift)**2)
        spectral_norm = parameters["FiducialArea"].v * parameters[
            "FiducialExposureTime"].v * dist_fac

        source_model.setup_model(data_source, redshift, spectral_norm)

        p_fields, v_fields, w_field = determine_fields(
            ds, source_model.source_type)

        if velocity_fields is not None:
            v_fields = velocity_fields

        if p_fields[0] == ("index", "x"):
            parameters["DataType"] = "cells"
        else:
            parameters["DataType"] = "particles"

        if hasattr(data_source, "left_edge"):
            # Region or grid
            le = data_source.left_edge
            re = data_source.right_edge
        elif hasattr(data_source,
                     "radius") and not hasattr(data_source, "height"):
            # Sphere
            le = -data_source.radius + data_source.center
            re = data_source.radius + data_source.center
        else:
            # Compute rough boundaries of the object
            # DOES NOT WORK for objects straddling periodic
            # boundaries yet
            if sum(ds.periodicity) > 0:
                mylog.warning("You are using a region that is not currently "
                              "supported for straddling periodic boundaries. "
                              "Check to make sure that this is not the case.")
            le = ds.arr(np.zeros(3), "code_length")
            re = ds.arr(np.zeros(3), "code_length")
            for i, ax in enumerate(p_fields):
                le[i], re[i] = data_source.quantities.extrema(ax)

        dds_min = get_smallest_dds(ds, parameters["DataType"])
        le = np.rint((le - ds.domain_left_edge) /
                     dds_min) * dds_min + ds.domain_left_edge
        re = ds.domain_right_edge - np.rint(
            (ds.domain_right_edge - re) / dds_min) * dds_min
        width = re - le
        parameters["Dimension"] = np.rint(width / dds_min).astype("int")
        parameters["Width"] = parameters["Dimension"] * dds_min.in_units("kpc")

        citer = data_source.chunks([], "io")

        photons = defaultdict(list)

        for chunk in parallel_objects(citer):

            chunk_data = source_model(chunk)

            if chunk_data is not None:
                number_of_photons, idxs, energies = chunk_data
                photons["NumberOfPhotons"].append(number_of_photons)
                photons["Energy"].append(ds.arr(energies, "keV"))
                photons["x"].append(chunk[p_fields[0]][idxs].in_units("kpc"))
                photons["y"].append(chunk[p_fields[1]][idxs].in_units("kpc"))
                photons["z"].append(chunk[p_fields[2]][idxs].in_units("kpc"))
                photons["vx"].append(chunk[v_fields[0]][idxs].in_units("km/s"))
                photons["vy"].append(chunk[v_fields[1]][idxs].in_units("km/s"))
                photons["vz"].append(chunk[v_fields[2]][idxs].in_units("km/s"))
                if w_field is None:
                    photons["dx"].append(ds.arr(np.zeros(len(idxs)), "kpc"))
                else:
                    photons["dx"].append(chunk[w_field][idxs].in_units("kpc"))

        source_model.cleanup_model()

        concatenate_photons(photons)

        # Translate photon coordinates to the source center
        # Fix photon coordinates for regions crossing a periodic boundary
        dw = ds.domain_width.to("kpc")
        for i, ax in enumerate("xyz"):
            if ds.periodicity[i] and len(photons[ax]) > 0:
                tfl = photons[ax] < le[i].to('kpc')
                tfr = photons[ax] > re[i].to('kpc')
                photons[ax][tfl] += dw[i]
                photons[ax][tfr] -= dw[i]
            photons[ax] -= parameters["center"][i].in_units("kpc")

        mylog.info("Finished generating photons.")
        mylog.info("Number of photons generated: %d" %
                   int(np.sum(photons["NumberOfPhotons"])))
        mylog.info("Number of cells with photons: %d" % len(photons["x"]))

        return cls(photons, parameters, cosmo)
Пример #15
0
    def write_fits_file(self, fitsfile, fov, nx, overwrite=False):
        """
        Write events to a FITS binary table file. The result is a
        standard "event file" which can be processed by standard
        X-ray analysis tools.

        Parameters
        ----------
        fitsfile : string
            The name of the event file to write.
        fov : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The field of view of the event file. If units are not 
            provided, they are assumed to be in arcminutes.
        nx : integer
            The resolution of the image (number of pixels on a side). 
        overwrite : boolean, optional
            Set to True to overwrite a previous file.
        """
        from astropy.time import Time, TimeDelta

        events = communicate_events(self.events)

        fov = parse_value(fov, "arcmin")

        if comm.rank == 0:

            exp_time = float(self.parameters["exp_time"])

            t_begin = Time.now()
            dt = TimeDelta(exp_time, format='sec')
            t_end = t_begin + dt

            dtheta = fov.to("deg").v / nx

            wcs = pywcs.WCS(naxis=2)
            wcs.wcs.crpix = [0.5*(nx+1)]*2
            wcs.wcs.crval = self.parameters["sky_center"].d
            wcs.wcs.cdelt = [-dtheta, dtheta]
            wcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
            wcs.wcs.cunit = ["deg"] * 2

            xx, yy = wcs.wcs_world2pix(self["xsky"].d, self["ysky"].d, 1)

            keepx = np.logical_and(xx >= 0.5, xx <= float(nx)+0.5)
            keepy = np.logical_and(yy >= 0.5, yy <= float(nx)+0.5)
            keep = np.logical_and(keepx, keepy)

            n_events = keep.sum()

            mylog.info("Threw out %d events because " % (xx.size-n_events) +
                       "they fell outside the field of view.")

            col_e = pyfits.Column(name='ENERGY', format='E', unit='eV',
                                  array=events["eobs"].in_units("eV").d[keep])
            col_x = pyfits.Column(name='X', format='D', unit='pixel',
                                  array=xx[keep])
            col_y = pyfits.Column(name='Y', format='D', unit='pixel',
                                  array=yy[keep])

            cols = [col_e, col_x, col_y]

            if "channel_type" in self.parameters:
                chantype = self.parameters["channel_type"]
                if chantype == "pha":
                    cunit = "adu"
                elif chantype == "pi":
                    cunit = "Chan"
                col_ch = pyfits.Column(name=chantype.upper(), format='1J',
                                       unit=cunit, array=events[chantype][keep])
                cols.append(col_ch)

                time = np.random.uniform(size=n_events, low=0.0,
                                         high=float(self.parameters["exp_time"]))
                col_t = pyfits.Column(name="TIME", format='1D', unit='s',
                                      array=time)
                cols.append(col_t)

            coldefs = pyfits.ColDefs(cols)
            tbhdu = pyfits.BinTableHDU.from_columns(coldefs)
            tbhdu.name = "EVENTS"

            tbhdu.header["MTYPE1"] = "sky"
            tbhdu.header["MFORM1"] = "x,y"
            tbhdu.header["MTYPE2"] = "EQPOS"
            tbhdu.header["MFORM2"] = "RA,DEC"
            tbhdu.header["TCTYP2"] = "RA---TAN"
            tbhdu.header["TCTYP3"] = "DEC--TAN"
            tbhdu.header["TCRVL2"] = float(self.parameters["sky_center"][0])
            tbhdu.header["TCRVL3"] = float(self.parameters["sky_center"][1])
            tbhdu.header["TCDLT2"] = -dtheta
            tbhdu.header["TCDLT3"] = dtheta
            tbhdu.header["TCRPX2"] = 0.5*(nx+1)
            tbhdu.header["TCRPX3"] = 0.5*(nx+1)
            tbhdu.header["TLMIN2"] = 0.5
            tbhdu.header["TLMIN3"] = 0.5
            tbhdu.header["TLMAX2"] = float(nx)+0.5
            tbhdu.header["TLMAX3"] = float(nx)+0.5
            if "channel_type" in self.parameters:
                rmf = RedistributionMatrixFile(self.parameters["rmf"])
                tbhdu.header["TLMIN4"] = rmf.cmin
                tbhdu.header["TLMAX4"] = rmf.cmax
                tbhdu.header["RESPFILE"] = os.path.split(self.parameters["rmf"])[-1]
                tbhdu.header["PHA_BINS"] = rmf.n_ch
                tbhdu.header["ANCRFILE"] = os.path.split(self.parameters["arf"])[-1]
                tbhdu.header["CHANTYPE"] = self.parameters["channel_type"]
                tbhdu.header["MISSION"] = self.parameters["mission"]
                tbhdu.header["TELESCOP"] = self.parameters["telescope"]
                tbhdu.header["INSTRUME"] = self.parameters["instrument"]
            tbhdu.header["EXPOSURE"] = exp_time
            tbhdu.header["TSTART"] = 0.0
            tbhdu.header["TSTOP"] = exp_time
            tbhdu.header["AREA"] = float(self.parameters["area"])
            tbhdu.header["HDUVERS"] = "1.1.0"
            tbhdu.header["RADECSYS"] = "FK5"
            tbhdu.header["EQUINOX"] = 2000.0
            tbhdu.header["HDUCLASS"] = "OGIP"
            tbhdu.header["HDUCLAS1"] = "EVENTS"
            tbhdu.header["HDUCLAS2"] = "ACCEPTED"
            tbhdu.header["DATE"] = t_begin.tt.isot
            tbhdu.header["DATE-OBS"] = t_begin.tt.isot
            tbhdu.header["DATE-END"] = t_end.tt.isot

            hdulist = [pyfits.PrimaryHDU(), tbhdu]

            if "channel_type" in self.parameters:
                start = pyfits.Column(name='START', format='1D', unit='s',
                                      array=np.array([0.0]))
                stop = pyfits.Column(name='STOP', format='1D', unit='s',
                                     array=np.array([exp_time]))

                tbhdu_gti = pyfits.BinTableHDU.from_columns([start,stop])
                tbhdu_gti.name = "STDGTI"
                tbhdu_gti.header["TSTART"] = 0.0
                tbhdu_gti.header["TSTOP"] = exp_time
                tbhdu_gti.header["HDUCLASS"] = "OGIP"
                tbhdu_gti.header["HDUCLAS1"] = "GTI"
                tbhdu_gti.header["HDUCLAS2"] = "STANDARD"
                tbhdu_gti.header["RADECSYS"] = "FK5"
                tbhdu_gti.header["EQUINOX"] = 2000.0
                tbhdu_gti.header["DATE"] = t_begin.tt.isot
                tbhdu_gti.header["DATE-OBS"] = t_begin.tt.isot
                tbhdu_gti.header["DATE-END"] = t_end.tt.isot

                hdulist.append(tbhdu_gti)

            pyfits.HDUList(hdulist).writeto(fitsfile, overwrite=overwrite)

        comm.barrier()
Пример #16
0
    def project_photons(self,
                        normal,
                        area_new=None,
                        exp_time_new=None,
                        redshift_new=None,
                        dist_new=None,
                        absorb_model=None,
                        sky_center=None,
                        no_shifting=False,
                        north_vector=None,
                        prng=None):
        r"""
        Projects photons onto an image plane given a line of sight.
        Returns a new :class:`~pyxsim.event_list.EventList`.

        Parameters
        ----------
        normal : character or array-like
            Normal vector to the plane of projection. If "x", "y", or "z", will
            assume to be along that axis (and will probably be faster). Otherwise,
            should be an off-axis normal vector, e.g [1.0, 2.0, -3.0]
        area_new : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            New value for the (constant) collecting area of the detector. If
            units are not specified, is assumed to be in cm**2.
        exp_time_new : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            The new value for the exposure time. If units are not specified
            it is assumed to be in seconds.
        redshift_new : float, optional
            The new value for the cosmological redshift.
        dist_new : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            The new value for the angular diameter distance, used for nearby sources.
            This may be optionally supplied instead of it being determined from the
            cosmology. If units are not specified, it is assumed to be in Mpc. To use this, the
            redshift must be zero.
        absorb_model : :class:`~pyxsim.spectral_models.AbsorptionModel`
            A model for foreground galactic absorption.
        sky_center : array-like, optional
            Center RA, Dec of the events in degrees.
        no_shifting : boolean, optional
            If set, the photon energies will not be Doppler shifted.
        north_vector : a sequence of floats
            A vector defining the "up" direction. This option sets the orientation of
            the plane of projection. If not set, an arbitrary grid-aligned north_vector
            is chosen. Ignored in the case where a particular axis (e.g., "x", "y", or
            "z") is explicitly specified.
        prng : :class:`~numpy.random.RandomState` object or :mod:`~numpy.random`, optional
            A pseudo-random number generator. Typically will only be specified
            if you have a reason to generate the same set of random numbers, such as for a
            test. Default is the :mod:`numpy.random` module.

        Examples
        --------
        >>> L = np.array([0.1,-0.2,0.3])
        >>> events = my_photons.project_photons(L, area_new=10000.,
        ...                                     redshift_new=0.05)
        """

        if prng is None:
            prng = np.random

        if redshift_new is not None and dist_new is not None:
            mylog.error("You may specify a new redshift or distance, " +
                        "but not both!")

        if sky_center is None:
            sky_center = YTArray([30., 45.], "degree")
        else:
            sky_center = YTArray(sky_center, "degree")

        dx = self.photons["dx"].d
        if isinstance(normal, string_types):
            # if on-axis, just use the maximum width of the plane perpendicular
            # to that axis
            w = self.parameters["Width"].copy()
            w["xyz".index(normal)] = 0.0
            ax_idx = np.argmax(w)
        else:
            # if off-axis, just use the largest width to make sure we get everything
            ax_idx = np.argmax(self.parameters["Width"])
        nx = self.parameters["Dimension"][ax_idx]
        dx_min = (self.parameters["Width"] /
                  self.parameters["Dimension"])[ax_idx]

        if not isinstance(normal, string_types):
            L = np.array(normal)
            orient = Orientation(L, north_vector=north_vector)
            x_hat = orient.unit_vectors[0]
            y_hat = orient.unit_vectors[1]
            z_hat = orient.unit_vectors[2]

        n_ph = self.photons["NumberOfPhotons"]
        n_ph_tot = n_ph.sum()

        parameters = {}

        zobs0 = self.parameters["FiducialRedshift"]
        D_A0 = self.parameters["FiducialAngularDiameterDistance"]
        scale_factor = 1.0

        if (exp_time_new is None and area_new is None and redshift_new is None
                and dist_new is None):
            my_n_obs = n_ph_tot
            zobs = zobs0
            D_A = D_A0
        else:
            if exp_time_new is None:
                Tratio = 1.
            else:
                exp_time_new = parse_value(exp_time_new, "s")
                Tratio = exp_time_new / self.parameters["FiducialExposureTime"]
            if area_new is None:
                Aratio = 1.
            else:
                area_new = parse_value(area_new, "cm**2")
                Aratio = area_new / self.parameters["FiducialArea"]
            if redshift_new is None and dist_new is None:
                Dratio = 1.
                zobs = zobs0
                D_A = D_A0
            else:
                if dist_new is not None:
                    if redshift_new is not None and redshift_new > 0.0:
                        mylog.warning(
                            "Redshift must be zero for nearby sources. Resetting redshift to 0.0."
                        )
                        zobs = 0.0
                    D_A = parse_value(dist_new, "Mpc")
                else:
                    zobs = redshift_new
                    D_A = self.cosmo.angular_diameter_distance(
                        0.0, zobs).in_units("Mpc")
                    scale_factor = (1. + zobs0) / (1. + zobs)
                Dratio = D_A0*D_A0*(1.+zobs0)**3 / \
                         (D_A*D_A*(1.+zobs)**3)
            fak = Aratio * Tratio * Dratio
            if fak > 1:
                raise ValueError(
                    "This combination of requested parameters results in "
                    "%g%% more photons collected than are " % (100. *
                                                               (fak - 1.)) +
                    "available in the sample. Please reduce the collecting "
                    "area, exposure time, or increase the distance/redshift "
                    "of the object. Alternatively, generate a larger sample "
                    "of photons.")
            my_n_obs = np.int64(n_ph_tot * fak)

        Nn = 4294967294
        if my_n_obs == n_ph_tot:
            if my_n_obs <= Nn:
                idxs = np.arange(my_n_obs, dtype='uint32')
            else:
                idxs = np.arange(my_n_obs, dtype='uint64')
        else:
            if n_ph_tot <= Nn:
                idxs = np.arange(n_ph_tot, dtype='uint32')
                prng.shuffle(idxs)
                idxs = idxs[:my_n_obs]
            else:
                Nc = np.int32(n_ph_tot / Nn)
                idxs = np.zeros(my_n_obs, dtype=np.uint64)
                Nup = np.uint32(my_n_obs / Nc)
                for i in range(Nc + 1):
                    if (i + 1) * Nc < n_ph_tot:
                        idtm = np.arange(i * Nc, (i + 1) * Nc, dtype='uint64')
                        Nupt = Nup
                    else:
                        idtm = np.arange(i * Nc, n_ph_tot, dtype='uint64')
                        Nupt = my_n_obs - i * Nup
                    prng.shuffle(idtm)
                    idxs[i * Nup, i * Nup + Nupt] = idtm[:Nupt]
                    del (idtm)
            # idxs = prng.permutation(n_ph_tot)[:my_n_obs].astype("int64")
        obs_cells = np.searchsorted(self.p_bins, idxs, side='right') - 1
        delta = dx[obs_cells]

        if isinstance(normal, string_types):

            if self.parameters["DataType"] == "cells":
                xsky = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
                ysky = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
            elif self.parameters["DataType"] == "particles":
                xsky = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
                ysky = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
            xsky *= delta
            ysky *= delta
            xsky += self.photons[axes_lookup[normal][0]].d[obs_cells]
            ysky += self.photons[axes_lookup[normal][1]].d[obs_cells]

            if not no_shifting:
                vz = self.photons["v%s" % normal]

        else:

            if self.parameters["DataType"] == "cells":
                x = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
                y = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
                z = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
            elif self.parameters["DataType"] == "particles":
                x = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
                y = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
                z = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)

            if not no_shifting:
                vz = self.photons["vx"]*z_hat[0] + \
                     self.photons["vy"]*z_hat[1] + \
                     self.photons["vz"]*z_hat[2]

            x *= delta
            y *= delta
            z *= delta
            x += self.photons["x"].d[obs_cells]
            y += self.photons["y"].d[obs_cells]
            z += self.photons["z"].d[obs_cells]

            xsky = x * x_hat[0] + y * x_hat[1] + z * x_hat[2]
            ysky = x * y_hat[0] + y * y_hat[1] + z * y_hat[2]

        del (delta)
        if no_shifting:
            eobs = self.photons["Energy"][idxs]
        else:
            # shift = -vz.in_cgs()/clight
            # shift = np.sqrt((1.-shift)/(1.+shift))
            # eobs = self.photons["Energy"][idxs]*shift[obs_cells]
            shift = -vz[obs_cells].in_cgs() / clight
            shift = np.sqrt((1. - shift) / (1. + shift))
            eobs = self.photons["Energy"][idxs]
            eobs *= shift
            del (shift)
        eobs *= scale_factor

        if absorb_model is None:
            detected = np.ones(eobs.shape, dtype='bool')
        else:
            detected = absorb_model.absorb_photons(eobs, prng=prng)

        events = {}

        dtheta = YTQuantity(np.rad2deg(dx_min / D_A), "degree")

        events["xpix"] = xsky[detected] / dx_min.v + 0.5 * (nx + 1)
        events["ypix"] = ysky[detected] / dx_min.v + 0.5 * (nx + 1)
        events["eobs"] = eobs[detected]

        events = comm.par_combine_object(events, datatype="dict", op="cat")

        num_events = len(events["xpix"])

        if comm.rank == 0:
            mylog.info("Total number of observed photons: %d" % num_events)

        if exp_time_new is None:
            parameters["ExposureTime"] = self.parameters[
                "FiducialExposureTime"]
        else:
            parameters["ExposureTime"] = exp_time_new
        if area_new is None:
            parameters["Area"] = self.parameters["FiducialArea"]
        else:
            parameters["Area"] = area_new
        parameters["Redshift"] = zobs
        parameters["AngularDiameterDistance"] = D_A.in_units("Mpc")
        parameters["sky_center"] = sky_center
        parameters["pix_center"] = np.array([0.5 * (nx + 1)] * 2)
        parameters["dtheta"] = dtheta

        return EventList(events, parameters)
Пример #17
0
    def setup_model(self, data_source, redshift, spectral_norm):
        self.redshift = redshift
        ptype = None
        if not self.nei and not isinstance(self.Zmet, float):
            Z_units = str(data_source.ds._get_field_info(self.Zmet).units)
            if Z_units in ["dimensionless", "", "code_metallicity"]:
                self.Zconvert = 1.0 / metal_abund[self.abund_table]
            elif Z_units == "Zsun":
                self.Zconvert = 1.0
            else:
                raise RuntimeError(
                    "I don't understand metallicity units of %s!" % Z_units)
        if self.num_var_elem > 0:
            for key, value in self.var_elem.items():
                if not isinstance(value, float):
                    if "^" in key:
                        elem = key.split("^")[0]
                    else:
                        elem = key
                    n_elem = elem_names.index(elem)
                    m_units = str(data_source.ds._get_field_info(value).units)
                    if m_units in ["dimensionless", "", "code_metallicity"]:
                        self.mconvert[key] = atomic_weights[1] / (
                            self.atable[n_elem] * atomic_weights[n_elem] *
                            solar_H_abund)
                    elif m_units == "Zsun":
                        self.mconvert[key] = 1.0
                    else:
                        raise RuntimeError(
                            "I don't understand units of %s for element %s!" %
                            (m_units, key))
        if self.emission_measure_field is None:
            found_dfield = [
                fd for fd in particle_dens_fields
                if fd in data_source.ds.field_list
            ]
            if len(found_dfield) > 0:
                ptype = found_dfield[0][0]

                def _emission_measure(field, data):
                    nenh = data[found_dfield[0]] * data['particle_mass']
                    nenh /= mp * mp
                    nenh.convert_to_units("cm**-3")
                    if data.has_field_parameter("X_H"):
                        X_H = data.get_field_parameter("X_H")
                    else:
                        X_H = primordial_H_abund
                    if (ptype,
                            'ElectronAbundance') in data_source.ds.field_list:
                        nenh *= X_H * data[ptype, 'ElectronAbundance']
                        nenh *= X_H * (1. -
                                       data[ptype, 'NeutralHydrogenAbundance'])
                    else:
                        nenh *= 0.5 * (1. + X_H) * X_H
                    return nenh

                data_source.ds.add_field((ptype, 'emission_measure'),
                                         function=_emission_measure,
                                         particle_type=True,
                                         units="cm**-3")
                self.emission_measure_field = (ptype, 'emission_measure')
            else:
                self.emission_measure_field = ('gas', 'emission_measure')
        mylog.info("Using emission measure field '(%s, %s)'." %
                   self.emission_measure_field)
        if self.temperature_field is None:
            found_tfield = [
                fd for fd in particle_temp_fields
                if fd in data_source.ds.derived_field_list
            ]
            if len(found_tfield) > 0:
                self.temperature_field = found_tfield[0]
                # What we have to do here is make sure that the temperature is set correctly
                # for SPH datasets that don't have the temperature field defined. What this
                # means is that we must set the mean molecular weight to the value for a
                # fully ionized gas if the ionization fraction is not available in the dataset.
                if self.temperature_field not in data_source.ds.field_list and ptype is not None:
                    if (ptype, 'ElectronAbundance'
                        ) not in data_source.ds.field_list:
                        if data_source.has_field_parameter("X_H"):
                            X_H = data_source.get_field_parameter("X_H")
                        else:
                            X_H = 0.76
                        data_source.set_field_parameter(
                            "mean_molecular_weight", 4.0 / (5 * X_H + 3))
            else:
                self.temperature_field = ('gas', 'temperature')
        mylog.info("Using temperature field '(%s, %s)'." %
                   self.temperature_field)
        self.spectral_model.prepare_spectrum(redshift)
        self.spectral_norm = spectral_norm
        if self.kT_scale == "linear":
            self.kT_bins = np.linspace(self.kT_min,
                                       self.kT_max,
                                       num=self.n_kT + 1)
        elif self.kT_scale == "log":
            self.kT_bins = np.logspace(np.log10(self.kT_min),
                                       np.log10(self.kT_max),
                                       num=self.n_kT + 1)
        self.dkT = np.diff(self.kT_bins)
        citer = data_source.chunks([], "io")
        num_cells = 0
        T_min = self.kT_min * K_per_keV
        T_max = self.kT_max * K_per_keV
        for chunk in parallel_objects(citer):
            T = chunk[self.temperature_field].d
            num_cells += np.count_nonzero((T > T_min) & (T < T_max))
        num_cells = comm.mpi_allreduce(num_cells)
        self.source_type = data_source.ds._get_field_info(
            self.emission_measure_field).name[0]
        self.pbar = get_pbar("Processing cells/particles ", num_cells)
Пример #18
0
    def project_photons(self, normal, sky_center, absorb_model=None,
                        nH=None, no_shifting=False, north_vector=None,
                        sigma_pos=None, kernel="top_hat", prng=None,
                        **kwargs):
        r"""
        Projects photons onto an image plane given a line of sight.
        Returns a new :class:`~pyxsim.event_list.EventList`.

        Parameters
        ----------
        normal : character or array-like
            Normal vector to the plane of projection. If "x", "y", or "z", will
            assume to be along that axis (and will probably be faster). Otherwise,
            should be an off-axis normal vector, e.g [1.0, 2.0, -3.0]
        sky_center : array-like
            Center RA, Dec of the events in degrees.
        absorb_model : string or :class:`~pyxsim.spectral_models.AbsorptionModel`
            A model for foreground galactic absorption, to simulate the
            absorption of events before being detected. This cannot be applied
            here if you already did this step previously in the creation of the
            :class:`~pyxsim.photon_list.PhotonList` instance. Known options for
            strings are "wabs" and "tbabs".
        nH : float, optional
            The foreground column density in units of 10^22 cm^{-2}. Only used
            if absorption is applied.
        no_shifting : boolean, optional
            If set, the photon energies will not be Doppler shifted.
        north_vector : a sequence of floats
            A vector defining the "up" direction. This option sets the
            orientation of the plane of projection. If not set, an arbitrary
            grid-aligned north_vector is chosen. Ignored in the case where a
            particular axis (e.g., "x", "y", or "z") is explicitly specified.
        sigma_pos : float, optional
            Apply a gaussian smoothing operation to the sky positions of the
            events. This may be useful when the binned events appear blocky due
            to their uniform distribution within simulation cells. However, this
            will move the events away from their originating position on the
            sky, and so may distort surface brightness profiles and/or spectra.
            Should probably only be used for visualization purposes. Supply a
            float here to smooth with a standard deviation with this fraction
            of the cell size. Default: None
        kernel : string, optional
            The kernel used when smoothing positions of X-rays originating from
            SPH particles, "gaussian" or "top_hat". Default: "top_hat".
        prng : integer or :class:`~numpy.random.RandomState` object 
            A pseudo-random number generator. Typically will only be specified
            if you have a reason to generate the same set of random numbers,
            such as for a test. Default is to use the :mod:`numpy.random`
            module.

        Examples
        --------
        >>> L = np.array([0.1,-0.2,0.3])
        >>> events = my_photons.project_photons(L, [30., 45.])
        """
        prng = parse_prng(prng)

        scale_shift = -1.0/clight.to("km/s")

        if "smooth_positions" in kwargs:
            issue_deprecation_warning("'smooth_positions' has been renamed to "
                                      "'sigma_pos' and the former is deprecated!")
            sigma_pos = kwargs["smooth_positions"]

        if "redshift_new" in kwargs or "area_new" in kwargs or \
            "exp_time_new" in kwargs or "dist_new" in kwargs:
            issue_deprecation_warning("Changing the redshift, distance, area, or "
                                      "exposure time has been deprecated in "
                                      "project_photons!")

        if sigma_pos is not None and self.parameters["data_type"] == "particles":
            raise RuntimeError("The 'smooth_positions' argument should not be used with "
                               "particle-based datasets!")

        if isinstance(absorb_model, string_types):
            if absorb_model not in absorb_models:
                raise KeyError("%s is not a known absorption model!" % absorb_model)
            absorb_model = absorb_models[absorb_model]
        if absorb_model is not None:
            if nH is None:
                raise RuntimeError("You specified an absorption model, but didn't "
                                   "specify a value for nH!")
            absorb_model = absorb_model(nH)

        sky_center = YTArray(sky_center, "degree")

        n_ph = self.photons["num_photons"]

        if not isinstance(normal, string_types):
            L = np.array(normal)
            orient = Orientation(L, north_vector=north_vector)
            x_hat = orient.unit_vectors[0]
            y_hat = orient.unit_vectors[1]
            z_hat = orient.unit_vectors[2]
        else:
            x_hat = np.zeros(3)
            y_hat = np.zeros(3)
            z_hat = np.zeros(3)

        parameters = {}

        D_A = self.parameters["fid_d_a"]

        events = {}

        eobs = self.photons["energy"].v

        if not no_shifting:
            if comm.rank == 0:
                mylog.info("Doppler-shifting photon energies.")
            if isinstance(normal, string_types):
                shift = self.photons["vel"][:,"xyz".index(normal)]*scale_shift
            else:
                shift = np.dot(self.photons["vel"], z_hat)*scale_shift
            doppler_shift(shift, n_ph, eobs)

        if absorb_model is None:
            det = np.ones(eobs.size, dtype='bool')
            num_det = eobs.size
        else:
            if comm.rank == 0:
                mylog.info("Foreground galactic absorption: using "
                           "the %s model and nH = %g." % (absorb_model._name, nH))
            det = absorb_model.absorb_photons(eobs, prng=prng)
            num_det = det.sum()

        events["eobs"] = YTArray(eobs[det], "keV")

        num_events = comm.mpi_allreduce(num_det)

        if comm.rank == 0:
            mylog.info("%d events have been detected." % num_events)

        if num_det > 0:

            if comm.rank == 0:
                mylog.info("Assigning positions to events.")

            if isinstance(normal, string_types):
                norm = "xyz".index(normal)
            else:
                norm = normal

            xsky, ysky = scatter_events(norm, prng, kernel,
                                        self.parameters["data_type"],
                                        num_det, det, self.photons["num_photons"],
                                        self.photons["pos"].d, self.photons["dx"].d,
                                        x_hat, y_hat)

            if self.parameters["data_type"] == "cells" and sigma_pos is not None:
                if comm.rank == 0:
                    mylog.info("Optionally smoothing sky positions.")
                sigma = sigma_pos*np.repeat(self.photons["dx"].d, n_ph)[det]
                xsky += sigma * prng.normal(loc=0.0, scale=1.0, size=num_det)
                ysky += sigma * prng.normal(loc=0.0, scale=1.0, size=num_det)

            d_a = D_A.to("kpc").v
            xsky /= d_a
            ysky /= d_a

            if comm.rank == 0:
                mylog.info("Converting pixel to sky coordinates.")

            pixel_to_cel(xsky, ysky, sky_center)

        else:

            xsky = []
            ysky = []

        events["xsky"] = YTArray(xsky, "degree")
        events["ysky"] = YTArray(ysky, "degree")

        parameters["exp_time"] = self.parameters["fid_exp_time"]
        parameters["area"] = self.parameters["fid_area"]
        parameters["sky_center"] = sky_center

        return EventList(events, parameters)
Пример #19
0
    def write_fits_file(self, fitsfile, fov, nx, overwrite=False):
        """
        Write events to a FITS binary table file. The result is a
        standard "event file" which can be processed by standard
        X-ray analysis tools.

        Parameters
        ----------
        fitsfile : string
            The name of the event file to write.
        fov : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
            The field of view of the event file. If units are not 
            provided, they are assumed to be in arcminutes.
        nx : integer
            The resolution of the image (number of pixels on a side). 
        overwrite : boolean, optional
            Set to True to overwrite a previous file.
        """
        from astropy.time import Time, TimeDelta

        events = communicate_events(self.events)

        fov = parse_value(fov, "arcmin")

        if comm.rank == 0:

            exp_time = float(self.parameters["exp_time"])

            t_begin = Time.now()
            dt = TimeDelta(exp_time, format='sec')
            t_end = t_begin + dt

            dtheta = fov.to("deg").v / nx

            wcs = pywcs.WCS(naxis=2)
            wcs.wcs.crpix = [0.5 * (nx + 1)] * 2
            wcs.wcs.crval = self.parameters["sky_center"].d
            wcs.wcs.cdelt = [-dtheta, dtheta]
            wcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
            wcs.wcs.cunit = ["deg"] * 2

            xx, yy = wcs.wcs_world2pix(self["xsky"].d, self["ysky"].d, 1)

            keepx = np.logical_and(xx >= 0.5, xx <= float(nx) + 0.5)
            keepy = np.logical_and(yy >= 0.5, yy <= float(nx) + 0.5)
            keep = np.logical_and(keepx, keepy)

            n_events = keep.sum()

            mylog.info("Threw out %d events because " % (xx.size - n_events) +
                       "they fell outside the field of view.")

            col_e = pyfits.Column(name='ENERGY',
                                  format='E',
                                  unit='eV',
                                  array=events["eobs"].in_units("eV").d[keep])
            col_x = pyfits.Column(name='X',
                                  format='D',
                                  unit='pixel',
                                  array=xx[keep])
            col_y = pyfits.Column(name='Y',
                                  format='D',
                                  unit='pixel',
                                  array=yy[keep])

            cols = [col_e, col_x, col_y]

            if "channel_type" in self.parameters:
                chantype = self.parameters["channel_type"]
                if chantype == "pha":
                    cunit = "adu"
                elif chantype == "pi":
                    cunit = "Chan"
                col_ch = pyfits.Column(name=chantype.upper(),
                                       format='1J',
                                       unit=cunit,
                                       array=events[chantype][keep])
                cols.append(col_ch)

                time = np.random.uniform(size=n_events,
                                         low=0.0,
                                         high=float(
                                             self.parameters["exp_time"]))
                col_t = pyfits.Column(name="TIME",
                                      format='1D',
                                      unit='s',
                                      array=time)
                cols.append(col_t)

            coldefs = pyfits.ColDefs(cols)
            tbhdu = pyfits.BinTableHDU.from_columns(coldefs)
            tbhdu.name = "EVENTS"

            tbhdu.header["MTYPE1"] = "sky"
            tbhdu.header["MFORM1"] = "x,y"
            tbhdu.header["MTYPE2"] = "EQPOS"
            tbhdu.header["MFORM2"] = "RA,DEC"
            tbhdu.header["TCTYP2"] = "RA---TAN"
            tbhdu.header["TCTYP3"] = "DEC--TAN"
            tbhdu.header["TCRVL2"] = float(self.parameters["sky_center"][0])
            tbhdu.header["TCRVL3"] = float(self.parameters["sky_center"][1])
            tbhdu.header["TCDLT2"] = -dtheta
            tbhdu.header["TCDLT3"] = dtheta
            tbhdu.header["TCRPX2"] = 0.5 * (nx + 1)
            tbhdu.header["TCRPX3"] = 0.5 * (nx + 1)
            tbhdu.header["TLMIN2"] = 0.5
            tbhdu.header["TLMIN3"] = 0.5
            tbhdu.header["TLMAX2"] = float(nx) + 0.5
            tbhdu.header["TLMAX3"] = float(nx) + 0.5
            if "channel_type" in self.parameters:
                rmf = RedistributionMatrixFile(self.parameters["rmf"])
                tbhdu.header["TLMIN4"] = rmf.cmin
                tbhdu.header["TLMAX4"] = rmf.cmax
                tbhdu.header["RESPFILE"] = os.path.split(
                    self.parameters["rmf"])[-1]
                tbhdu.header["PHA_BINS"] = rmf.n_ch
                tbhdu.header["ANCRFILE"] = os.path.split(
                    self.parameters["arf"])[-1]
                tbhdu.header["CHANTYPE"] = self.parameters["channel_type"]
                tbhdu.header["MISSION"] = self.parameters["mission"]
                tbhdu.header["TELESCOP"] = self.parameters["telescope"]
                tbhdu.header["INSTRUME"] = self.parameters["instrument"]
            tbhdu.header["EXPOSURE"] = exp_time
            tbhdu.header["TSTART"] = 0.0
            tbhdu.header["TSTOP"] = exp_time
            tbhdu.header["AREA"] = float(self.parameters["area"])
            tbhdu.header["HDUVERS"] = "1.1.0"
            tbhdu.header["RADECSYS"] = "FK5"
            tbhdu.header["EQUINOX"] = 2000.0
            tbhdu.header["HDUCLASS"] = "OGIP"
            tbhdu.header["HDUCLAS1"] = "EVENTS"
            tbhdu.header["HDUCLAS2"] = "ACCEPTED"
            tbhdu.header["DATE"] = t_begin.tt.isot
            tbhdu.header["DATE-OBS"] = t_begin.tt.isot
            tbhdu.header["DATE-END"] = t_end.tt.isot

            hdulist = [pyfits.PrimaryHDU(), tbhdu]

            if "channel_type" in self.parameters:
                start = pyfits.Column(name='START',
                                      format='1D',
                                      unit='s',
                                      array=np.array([0.0]))
                stop = pyfits.Column(name='STOP',
                                     format='1D',
                                     unit='s',
                                     array=np.array([exp_time]))

                tbhdu_gti = pyfits.BinTableHDU.from_columns([start, stop])
                tbhdu_gti.name = "STDGTI"
                tbhdu_gti.header["TSTART"] = 0.0
                tbhdu_gti.header["TSTOP"] = exp_time
                tbhdu_gti.header["HDUCLASS"] = "OGIP"
                tbhdu_gti.header["HDUCLAS1"] = "GTI"
                tbhdu_gti.header["HDUCLAS2"] = "STANDARD"
                tbhdu_gti.header["RADECSYS"] = "FK5"
                tbhdu_gti.header["EQUINOX"] = 2000.0
                tbhdu_gti.header["DATE"] = t_begin.tt.isot
                tbhdu_gti.header["DATE-OBS"] = t_begin.tt.isot
                tbhdu_gti.header["DATE-END"] = t_end.tt.isot

                hdulist.append(tbhdu_gti)

            pyfits.HDUList(hdulist).writeto(fitsfile, overwrite=overwrite)

        comm.barrier()
Пример #20
0
    def write_fits_file(self, fitsfile, clobber=False):
        """
        Write events to a FITS binary table file with filename *fitsfile*.
        Set *clobber* to True if you need to overwrite a previous file.
        """
        from astropy.time import Time, TimeDelta
        pyfits = _astropy.pyfits

        exp_time = float(self.parameters["ExposureTime"])

        t_begin = Time.now()
        dt = TimeDelta(exp_time, format='sec')
        t_end = t_begin + dt

        col_e = pyfits.Column(name='ENERGY', format='E', unit='eV',
                              array=self["eobs"].in_units("eV").d)
        col_x = pyfits.Column(name='X', format='D', unit='pixel',
                              array=self["xpix"])
        col_y = pyfits.Column(name='Y', format='D', unit='pixel',
                              array=self["ypix"])

        cols = [col_e, col_x, col_y]

        if "ChannelType" in self.parameters:
             chantype = self.parameters["ChannelType"]
             if chantype == "PHA":
                  cunit = "adu"
             elif chantype == "PI":
                  cunit = "Chan"
             col_ch = pyfits.Column(name=chantype.upper(), format='1J',
                                    unit=cunit, array=self.events[chantype])
             cols.append(col_ch)

             mylog.info("Generating times for events assuming uniform time "
                        "distribution. In future versions this will be made "
                        "more general.")

             time = np.random.uniform(size=self.num_events, low=0.0,
                                      high=float(self.parameters["ExposureTime"]))
             col_t = pyfits.Column(name="TIME", format='1D', unit='s',
                                   array=time)
             cols.append(col_t)

        coldefs = pyfits.ColDefs(cols)
        tbhdu = pyfits.BinTableHDU.from_columns(coldefs)
        tbhdu.name = "EVENTS"

        tbhdu.header["MTYPE1"] = "sky"
        tbhdu.header["MFORM1"] = "x,y"
        tbhdu.header["MTYPE2"] = "EQPOS"
        tbhdu.header["MFORM2"] = "RA,DEC"
        tbhdu.header["TCTYP2"] = "RA---TAN"
        tbhdu.header["TCTYP3"] = "DEC--TAN"
        tbhdu.header["TCRVL2"] = float(self.parameters["sky_center"][0])
        tbhdu.header["TCRVL3"] = float(self.parameters["sky_center"][1])
        tbhdu.header["TCDLT2"] = -float(self.parameters["dtheta"])
        tbhdu.header["TCDLT3"] = float(self.parameters["dtheta"])
        tbhdu.header["TCRPX2"] = self.parameters["pix_center"][0]
        tbhdu.header["TCRPX3"] = self.parameters["pix_center"][1]
        tbhdu.header["TLMIN2"] = 0.5
        tbhdu.header["TLMIN3"] = 0.5
        tbhdu.header["TLMAX2"] = 2.*self.parameters["pix_center"][0]-0.5
        tbhdu.header["TLMAX3"] = 2.*self.parameters["pix_center"][1]-0.5
        if "ChannelType" in self.parameters:
            rmf = RedistributionMatrixFile(self.parameters["RMF"])
            tbhdu.header["TLMIN4"] = rmf.cmin
            tbhdu.header["TLMAX4"] = rmf.cmax
            tbhdu.header["RESPFILE"] = os.path.split(self.parameters["RMF"])[-1]
            tbhdu.header["PHA_BINS"] = rmf.n_ch
        tbhdu.header["EXPOSURE"] = exp_time
        tbhdu.header["TSTART"] = 0.0
        tbhdu.header["TSTOP"] = exp_time
        tbhdu.header["AREA"] = float(self.parameters["Area"])
        if "AngularDiameterDistance" in self.parameters:
            tbhdu.header["D_A"] = float(self.parameters["AngularDiameterDistance"])
        if "Redshift" in self.parameters:
            tbhdu.header["REDSHIFT"] = self.parameters["Redshift"]
        tbhdu.header["HDUVERS"] = "1.1.0"
        tbhdu.header["RADECSYS"] = "FK5"
        tbhdu.header["EQUINOX"] = 2000.0
        tbhdu.header["HDUCLASS"] = "OGIP"
        tbhdu.header["HDUCLAS1"] = "EVENTS"
        tbhdu.header["HDUCLAS2"] = "ACCEPTED"
        tbhdu.header["DATE"] = t_begin.tt.isot
        tbhdu.header["DATE-OBS"] = t_begin.tt.isot
        tbhdu.header["DATE-END"] = t_end.tt.isot
        if "ARF" in self.parameters:
            tbhdu.header["ANCRFILE"] = os.path.split(self.parameters["ARF"])[-1]
        if "ChannelType" in self.parameters:
            tbhdu.header["CHANTYPE"] = self.parameters["ChannelType"]
        if "Mission" in self.parameters:
            tbhdu.header["MISSION"] = self.parameters["Mission"]
        if "Telescope" in self.parameters:
            tbhdu.header["TELESCOP"] = self.parameters["Telescope"]
        if "Instrument" in self.parameters:
            tbhdu.header["INSTRUME"] = self.parameters["Instrument"]

        hdulist = [pyfits.PrimaryHDU(), tbhdu]

        if "ChannelType" in self.parameters:
            start = pyfits.Column(name='START', format='1D', unit='s',
                                  array=np.array([0.0]))
            stop = pyfits.Column(name='STOP', format='1D', unit='s',
                                 array=np.array([exp_time]))

            tbhdu_gti = pyfits.BinTableHDU.from_columns([start,stop])
            tbhdu_gti.name = "STDGTI"
            tbhdu_gti.header["TSTART"] = 0.0
            tbhdu_gti.header["TSTOP"] = exp_time
            tbhdu_gti.header["HDUCLASS"] = "OGIP"
            tbhdu_gti.header["HDUCLAS1"] = "GTI"
            tbhdu_gti.header["HDUCLAS2"] = "STANDARD"
            tbhdu_gti.header["RADECSYS"] = "FK5"
            tbhdu_gti.header["EQUINOX"] = 2000.0
            tbhdu_gti.header["DATE"] = t_begin.tt.isot
            tbhdu_gti.header["DATE-OBS"] = t_begin.tt.isot
            tbhdu_gti.header["DATE-END"] = t_end.tt.isot

            hdulist.append(tbhdu_gti)

        pyfits.HDUList(hdulist).writeto(fitsfile, clobber=clobber)
Пример #21
0
    def from_file(cls, filename):
        r"""
        Initialize a :class:`~pyxsim.photon_list.PhotonList` from 
        the HDF5 file *filename*.
        """

        mylog.info("Reading photons from %s." % filename)

        photons = {}
        parameters = {}

        f = h5py.File(filename, "r")

        p = f["/parameters"]
        parameters["fid_exp_time"] = YTQuantity(p["fid_exp_time"].value, "s")
        parameters["fid_area"] = YTQuantity(p["fid_area"].value, "cm**2")
        parameters["fid_redshift"] = p["fid_redshift"].value
        parameters["fid_d_a"] = YTQuantity(p["fid_d_a"].value, "Mpc")
        parameters["hubble"] = p["hubble"].value
        parameters["omega_matter"] = p["omega_matter"].value
        parameters["omega_lambda"] = p["omega_lambda"].value
        if "data_type" in p:
            parameters["data_type"] = force_unicode(p["data_type"].value)
        else:
            parameters["data_type"] = "cells"

        d = f["/data"]

        num_cells = d["x"].size
        start_c = comm.rank*num_cells//comm.size
        end_c = (comm.rank+1)*num_cells//comm.size

        photons["pos"] = YTArray(np.zeros((num_cells, 3)), "kpc")
        photons["vel"] = YTArray(np.zeros((num_cells, 3)), "km/s")
        photons["pos"][:, 0] = d["x"][start_c:end_c]
        photons["pos"][:, 1] = d["y"][start_c:end_c]
        photons["pos"][:, 2] = d["z"][start_c:end_c]
        photons["vel"][:, 0] = d["vx"][start_c:end_c]
        photons["vel"][:, 1] = d["vy"][start_c:end_c]
        photons["vel"][:, 2] = d["vz"][start_c:end_c]
        photons["dx"] = YTArray(d["dx"][start_c:end_c], "kpc")

        n_ph = d["num_photons"][:]

        if comm.rank == 0:
            start_e = np.int64(0)
        else:
            start_e = n_ph[:start_c].sum()
        end_e = start_e + np.int64(n_ph[start_c:end_c].sum())

        photons["num_photons"] = n_ph[start_c:end_c]
        photons["energy"] = YTArray(d["energy"][start_e:end_e], "keV")

        f.close()

        cosmo = Cosmology(hubble_constant=parameters["hubble"],
                          omega_matter=parameters["omega_matter"],
                          omega_lambda=parameters["omega_lambda"])

        mylog.info("Read %d photons from %d %s." % (n_ph.sum(), num_cells, 
                                                    parameters["data_type"]))

        return cls(photons, parameters, cosmo)
Пример #22
0
    def write_spectrum(self, specfile, bin_type="channel", emin=0.1,
                       emax=10.0, nchan=2000, clobber=False):
        r"""
        Bin event energies into a spectrum and write it to a FITS binary table. Can bin
        on energy or channel. In the latter case, the spectral binning will be determined by
        the RMF binning.

        Parameters
        ----------
        specfile : string
            The name of the FITS file to be written.
        bin_type : string, optional
            Bin on "energy" or "channel". If an RMF is detected, channel information will be
            imported from it. 
        emin : float, optional
            The minimum energy of the spectral bins in keV. Only used if binning without an RMF.
        emax : float, optional
            The maximum energy of the spectral bins in keV. Only used if binning without an RMF.
        nchan : integer, optional
            The number of channels. Only used if binning without an RMF.
        """
        pyfits = _astropy.pyfits
        if bin_type == "channel" and "ChannelType" in self.parameters:
            spectype = self.parameters["ChannelType"]
            rmf = RedistributionMatrixFile(self.parameters["RMF"])
            minlength = rmf.n_ch
            if rmf.cmin == 1: minlength += 1
            spec = np.bincount(self[spectype], minlength=minlength)
            if rmf.cmin == 1: spec = spec[1:]
            bins = (np.arange(rmf.n_ch)+rmf.cmin).astype("int32")
        else:
            espec = self["eobs"].d
            erange = (emin, emax)
            spec, ee = np.histogram(espec, bins=nchan, range=erange)
            if bin_type == "energy":
                bins = 0.5*(ee[1:]+ee[:-1])
                spectype = "energy"
            else:
                mylog.info("Events haven't been convolved with an RMF, so assuming "
                           "a perfect response and %d PI channels." % nchan)
                bins = (np.arange(nchan)+1).astype("int32")
                spectype = "pi"

        col1 = pyfits.Column(name='CHANNEL', format='1J', array=bins)
        col2 = pyfits.Column(name=spectype.upper(), format='1D', array=bins.astype("float64"))
        col3 = pyfits.Column(name='COUNTS', format='1J', array=spec.astype("int32"))
        col4 = pyfits.Column(name='COUNT_RATE', format='1D', array=spec/float(self.parameters["ExposureTime"]))

        coldefs = pyfits.ColDefs([col1, col2, col3, col4])

        tbhdu = pyfits.BinTableHDU.from_columns(coldefs)
        tbhdu.name = "SPECTRUM"

        tbhdu.header["DETCHANS"] = spec.shape[0]
        tbhdu.header["TOTCTS"] = spec.sum()
        tbhdu.header["EXPOSURE"] = float(self.parameters["ExposureTime"])
        tbhdu.header["LIVETIME"] = float(self.parameters["ExposureTime"])
        tbhdu.header["CONTENT"] = spectype
        tbhdu.header["HDUCLASS"] = "OGIP"
        tbhdu.header["HDUCLAS1"] = "SPECTRUM"
        tbhdu.header["HDUCLAS2"] = "TOTAL"
        tbhdu.header["HDUCLAS3"] = "TYPE:I"
        tbhdu.header["HDUCLAS4"] = "COUNT"
        tbhdu.header["HDUVERS"] = "1.1.0"
        tbhdu.header["HDUVERS1"] = "1.1.0"
        tbhdu.header["CHANTYPE"] = spectype
        tbhdu.header["BACKFILE"] = "none"
        tbhdu.header["CORRFILE"] = "none"
        tbhdu.header["POISSERR"] = True
        if "RMF" in self.parameters:
            tbhdu.header["RESPFILE"] = os.path.split(self.parameters["RMF"])[-1]
        else:
            tbhdu.header["RESPFILE"] = "none"
        if "ARF" in self.parameters:
            tbhdu.header["ANCRFILE"] = os.path.split(self.parameters["ARF"])[-1]
        else:
            tbhdu.header["ANCRFILE"] = "none"
        if "Mission" in self.parameters:
            tbhdu.header["MISSION"] = self.parameters["Mission"]
        else:
            tbhdu.header["MISSION"] = "none"
        if "Telescope" in self.parameters:
            tbhdu.header["TELESCOP"] = self.parameters["Telescope"]
        else:
            tbhdu.header["TELESCOP"] = "none"
        if "Instrument" in self.parameters:
            tbhdu.header["INSTRUME"] = self.parameters["Instrument"]
        else:
            tbhdu.header["INSTRUME"] = "none"
        tbhdu.header["AREASCAL"] = 1.0
        tbhdu.header["CORRSCAL"] = 0.0
        tbhdu.header["BACKSCAL"] = 1.0

        hdulist = pyfits.HDUList([pyfits.PrimaryHDU(), tbhdu])

        hdulist.writeto(specfile, clobber=clobber)
Пример #23
0
def make_xrb_particles(data_source,
                       age_field,
                       scale_length,
                       sfr_time_range=(1.0, "Gyr"),
                       prng=None):
    r"""
    This routine generates an in-memory dataset composed of X-ray binary particles
    from an input data source containing star particles. 

    Parameters
    ----------
    data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer`
        The yt data source to obtain the data from, such as a sphere, box, disk, 
        etc.
    age_field : string or (type, name) field tuple
        The stellar age field. Must be in some kind of time units. 
    scale_length : string, (ftype, fname) tuple, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`
        The radial length scale over which to scatter the XRB particles
        from their parent star particle. Can be the name of a smoothing
        length field for the stars, a (value, unit) tuple, or a YTQuantity.
    sfr_time_range : string, (ftype, fname) tuple, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity`, optional
        The recent time range over which to calculate the star formation rate from
        the current time in the dataset. Default: 1.0 Gyr
    prng : integer or :class:`~numpy.random.RandomState` object 
        A pseudo-random number generator. Typically will only be specified
        if you have a reason to generate the same set of random numbers, such as for a
        test. Default is to use the :mod:`numpy.random` module.
    """
    prng = parse_prng(prng)

    ds = data_source.ds

    ptype = data_source._determine_fields(age_field)[0][0]

    t = data_source[age_field].to("Gyr")
    m = data_source[(ptype, "particle_mass")].to("Msun")

    sfr_time_range = parse_value(sfr_time_range, "Gyr")

    recent = t < sfr_time_range

    n_recent = recent.sum()

    if n_recent == 0:
        sfr = 0.0
    else:
        sfr = (m[recent].sum() / sfr_time_range).to("Msun/yr").v

    mylog.info("%d star particles were formed in the last " % n_recent +
               "%s for a SFR of %4.1f Msun/yr." % (sfr_time_range, sfr))

    mtot = m.sum()

    npart = m.size

    scale_field = None
    if isinstance(scale_length, tuple):
        if isinstance(scale_length[0], string_types):
            scale_field = scale_length
    elif isinstance(scale_length, string_types):
        scale_field = (ptype, scale_length)

    if scale_field is None:
        if isinstance(scale_length, tuple):
            scale = YTArray([scale_length[0]] * npart, scale_length[1])
        elif isinstance(scale_length, YTQuantity):
            scale = YTArray([scale_length] * npart)
        else:
            scale = YTArray([scale_length[0]] * npart, "kpc")
    else:
        scale = data_source[scale_length]

    scale = scale.to('kpc').d

    N_l = lmxb_cdf(Lcut) * mtot.v * 1.0e-11
    N_h = hmxb_cdf(Lcut) * sfr

    N_all = N_l + N_h

    if N_all == 0.0:
        raise RuntimeError("There are no X-ray binaries to generate!")

    # Compute conversion factors from luminosity to count rate

    lmxb_factor = get_scale_factor(alpha_lmxb, emin_lmxb, emax_lmxb)
    hmxb_factor = get_scale_factor(alpha_hmxb, emin_hmxb, emax_hmxb)

    xp = []
    yp = []
    zp = []
    vxp = []
    vyp = []
    vzp = []
    lp = []
    rp = []
    ap = []

    if N_l > 0.0:

        F_l = np.zeros(nbins + 1)
        for i in range(1, nbins + 1):
            F_l[i] = lmxb_cdf(Lbins[i])
        F_l /= F_l[-1]
        invcdf_l = InterpolatedUnivariateSpline(F_l, logLbins)

        n_l = prng.poisson(lam=N_l * m / mtot)

        mylog.info("Number of low-mass X-ray binaries: %s" % n_l.sum())

        for i, n in enumerate(n_l):
            if n > 0:
                randvec = prng.uniform(size=n)
                l = YTArray(10**invcdf_l(randvec) * 1.0e38, "erg/s")
                r = YTArray(l.v * lmxb_factor, "photons/s/keV")
                # Now convert output luminosities to bolometric
                l *= bc_lmxb
                x = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                y = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                z = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                x += data_source[ptype, "particle_position_x"][i].to("kpc")
                y += data_source[ptype, "particle_position_y"][i].to("kpc")
                z += data_source[ptype, "particle_position_z"][i].to("kpc")
                vx = YTArray([data_source[ptype, "particle_velocity_x"][i]] *
                             n).to('km/s')
                vy = YTArray([data_source[ptype, "particle_velocity_y"][i]] *
                             n).to('km/s')
                vz = YTArray([data_source[ptype, "particle_velocity_z"][i]] *
                             n).to('km/s')
                xp.append(x)
                yp.append(y)
                zp.append(z)
                vxp.append(vx)
                vyp.append(vy)
                vzp.append(vz)
                lp.append(l)
                rp.append(r)
                ap.append(np.array([alpha_lmxb] * n))

    if N_h > 0.0:

        F_h = np.zeros(nbins + 1)
        for i in range(1, nbins + 1):
            F_h[i] = hmxb_cdf(Lbins[i])
        F_h /= F_h[-1]
        invcdf_h = InterpolatedUnivariateSpline(F_h, logLbins)

        n_h = prng.poisson(lam=N_h * m / mtot)

        mylog.info("Number of high-mass X-ray binaries: %s" % n_h.sum())

        for i, n in enumerate(n_h):
            if n > 0:
                randvec = prng.uniform(size=n)
                l = YTArray(10**invcdf_h(randvec) * 1.0e38, "erg/s")
                r = YTArray(l.v * hmxb_factor, "photons/s/keV")
                # Now convert output luminosities to bolometric
                l *= bc_hmxb
                x = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                y = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                z = YTArray(prng.normal(scale=scale[i], size=n), "kpc")
                x += data_source[ptype, "particle_position_x"][i].to("kpc")
                y += data_source[ptype, "particle_position_y"][i].to("kpc")
                z += data_source[ptype, "particle_position_z"][i].to("kpc")
                vx = YTArray([data_source[ptype, "particle_velocity_x"][i]] *
                             n).to('km/s')
                vy = YTArray([data_source[ptype, "particle_velocity_y"][i]] *
                             n).to('km/s')
                vz = YTArray([data_source[ptype, "particle_velocity_z"][i]] *
                             n).to('km/s')
                xp.append(x)
                yp.append(y)
                zp.append(z)
                vxp.append(vx)
                vyp.append(vy)
                vzp.append(vz)
                lp.append(l)
                rp.append(r)
                ap.append(np.array([alpha_hmxb] * n))

    xp = uconcatenate(xp)
    yp = uconcatenate(yp)
    zp = uconcatenate(zp)
    vxp = uconcatenate(vxp)
    vyp = uconcatenate(vyp)
    vzp = uconcatenate(vzp)
    lp = uconcatenate(lp)
    rp = uconcatenate(rp)
    ap = uconcatenate(ap)

    data = {
        "particle_position_x": (xp.d, str(xp.units)),
        "particle_position_y": (yp.d, str(yp.units)),
        "particle_position_z": (zp.d, str(zp.units)),
        "particle_velocity_x": (vxp.d, str(vxp.units)),
        "particle_velocity_y": (vyp.d, str(vyp.units)),
        "particle_velocity_z": (vzp.d, str(vzp.units)),
        "particle_luminosity": (lp.d, str(lp.units)),
        "particle_count_rate": (rp.d, str(rp.units)),
        "particle_spectral_index": ap
    }

    dle = ds.domain_left_edge.to("kpc").v
    dre = ds.domain_right_edge.to("kpc").v

    bbox = np.array([[dle[i], dre[i]] for i in range(3)])

    new_ds = load_particles(data,
                            bbox=bbox,
                            length_unit="kpc",
                            time_unit="Myr",
                            mass_unit="Msun",
                            velocity_unit="km/s")

    return new_ds