Пример #1
0
 def new_molecule(self,
                  xgas=None,
                  xdep=None,
                  molecule=None,
                  column=None,
                  ndiss=None,
                  tfreeze=None):
     """Redefine the molecular abundance without recalculating density."""
     if molecule is not None:
         molecule = molecule.lower()
         self.molecule = molecule
         self.rates = ratefile('%s%s.dat' % (self.rates_path, molecule))
     if xgas is not None:
         self.xgas = self._make_profile(xgas)
     if xdep is not None:
         self.xdep = self._make_profile(xdep)
     if column is None:
         self.column = None
     else:
         self.column = self._make_profile(column)
     if tfreeze is not None:
         self.tfreeze = tfreeze
     if ndiss is not None:
         self.ndiss = ndiss
     self.abun = self._calc_abundance()
     self.column = self._calc_columndensity()
     return
Пример #2
0
    def __init__(self, path, **kwargs):
        """Load up the pre-calculated rates using get_radex_grid.py."""
        self.path = path
        self.filename = self.path.split('/')[-1]
        self.molecule = self.filename.split('_')[0]
        self.mu = ratefile(self.molecule).mu

        # The grid follows the standards in calc_radexgrid.py with the filename
        # providing the information about the axes:
        #       species_widths_temperatures_densities_columns.npy
        # where each variable contains the three values,
        #       minval_maxval_nvals,
        # with number in the '%.2f' format.

        self.grid = np.load(self.path)
        self.grid = np.where(np.isfinite(self.grid), self.grid, 0.0)
        self.parse_filename()

        # Default values to help with the interpolation.
        # To make sure that the slabs are sufficiently optically thin, we can
        # check, if checktau == True, if the optical depth of each slab is less
        # than or equal to tauthick.

        self.checktau = kwargs.get('checktau', False)
        self.tauthick = kwargs.get('tauthick', 0.66)
        self.verbose = kwargs.get('verbose', False)
        self.fwhm = 2. * np.sqrt(np.log(2) * 2)

        return
Пример #3
0
 def readtransition(self):
     """Infer the LAMDA transition number from frequency."""
     path = os.getenv('PYTHONPATH') + '/limepy/aux/'
     self.rates = ratefile(path + self.molecule + '.dat')
     for j in self.rates.lines.keys():
         if self.rates.lines[j].freq == self.nu:
             return j-1
     raise ValueError('No transition found. Check molecule is correct.')
Пример #4
0
def runRADEX(species, widths, temperatures, densities, columns, **kwargs):
    """Calls pyradex iteratively to produce a table of intensities."""

    # Make sure all the provided values are iterable.

    temperatures, tnum, tmin, tmax = formatInput(temperatures, log=False)
    densities, dnum, dmin, dmax = formatInput(densities, log=True)
    columns, cnum, cmin, cmax = formatInput(columns, log=True)
    widths, wnum, wmin, wmax = formatInput(widths, log=False)

    # Check that the collisional rate file exists.
    # This is hardwired to where the collisional rates are.

    rates_path = os.path.expanduser(os.getenv('RADEX_DATAPATH'))
    print('{}/{}.dat'.format(rates_path, species))
    if not os.path.isfile('{}/{}.dat'.format(rates_path, species)):
        raise ValueError('Did not find collisional rates.')
    rates = ratefile('{}/{}.dat'.format(rates_path, species))

    # We assume that the density is n(H2) and the ortho/para ratio is 3.
    # Check if oH2 and pH2 are valid colliders in the collisional rate file.
    # If they are, recalculate the densities.

    opr = kwargs.get('opr', False)
    if ('oH2' in rates.partners and 'pH2' in rates.partners and opr):
        opr_flag = True
        # print 'Assuming an ortho / para ratio of {}.'.format(opr)
    else:
        opr_flag = False


    # Dummy array to hold the results.
    # Hold up the 'jmax' transition, default is 10.
    # Saves both the brightness temperature and optical depth.

    jmax = kwargs.get('jmax', 9) + 1
    Tb = np.zeros((jmax, 3, wnum, tnum, dnum, cnum))

    # First initialise pyradex, then iterate through all the permutations.
    # Select the correct escape problem geometry, by default we assume slab.

    escapegeom = kwargs.pop('escapegeom', 'slab')
    radex = pyradex.Radex(species=species, temperature=temperatures[0],
                          density=densityDict(densities[0], opr, opr_flag),
                          column=columns[0], deltav=widths[0],
                          escapeProbGeom=escapegeom, **kwargs)

    t0 = time.time()
    tlast = np.nan
    for l, width in enumerate(widths):
        radex.deltav = width
        for t, temp in enumerate(temperatures):
            radex.temperature = temp
            for d, dens in enumerate(densities):
                radex.density = densityDict(dens, opr, opr_flag)
                for c, col in enumerate(columns):
                    radex.column = col

                    # Reload the molfile if temperature has changed.
                    with np.errstate(divide='ignore'):
                        if tlast == temp:
                            radex.run_radex()
                        else:
                            radex.run_radex(reload_molfile=True)
                        tlast = temp

                    # Parse the results.
                    Tb[:, 0, l, t, d, c] = radex.T_B[:jmax]
                    Tb[:, 1, l, t, d, c] = radex.Tex[:jmax]
                    Tb[:, 2, l, t, d, c] = radex.tau[:jmax]

    Tb = np.nan_to_num(Tb)
    t1 = time.time()

    if kwargs.get('verbose', True):
        print('Generated table in {}.'.format(seconds2hms(t1-t0)))

    # Save the file.
    fn = '{}_'.format(species)
    fn += '{:.2f}_{:.2f}_{:d}_'.format(wmin, wmax, wnum)
    fn += '{:.2f}_{:.2f}_{:d}_'.format(tmin, tmax, tnum)
    fn += '{:.2f}_{:.2f}_{:d}_'.format(dmin, dmax, dnum)
    fn += '{:.2f}_{:.2f}_{:d}.npy'.format(cmin, cmax, cnum)
    np.save(kwargs.get('path', './') + fn, Tb)

    return
Пример #5
0
    def __init__(self, **kwargs):
        """Protoplanetary disk model."""
        self.verbose = kwargs.get('verbose', True)

        # Model grid - cartesian grid.
        # Can specify the inner and outer radii with the number of grid points.
        # By default the grid will sample 500 points between [0, 7] * rval0 in
        # the radial direction and [0, 5] * rval0 in the vertical.
        # TODO: Include the option to have logarithmic values.

        self.rval0 = kwargs.get('rval0', 20.)
        self.rmin = kwargs.get('rmin', 0.)
        self.rmax = kwargs.get('rmax', 6. * self.rval0)
        self.nr = kwargs.get('nr', 500)

        self.zmin = kwargs.get('zmin', 0.)
        self.zmax = kwargs.get('zmax', 3. * self.rval0)
        self.nz = kwargs.get('nz', 500)

        self.rvals = np.linspace(self.rmin, self.rmax, self.nr)
        self.zvals = np.linspace(self.zmin, self.zmax, self.nz)
        self.rpnts = self.rvals[None, :] * np.ones(self.nz)[:, None]
        self.zpnts = np.ones(self.nr)[None, :] * self.zvals[:, None]

        self.rvals_m = self.rvals * sc.au
        self.zvals_m = self.zvals * sc.au
        self.rvals_cm = self.rvals_m * 1e2
        self.zvals_cm = self.zvals_m * 1e2

        # System masses. In solar masses.

        self.mstar = kwargs.get('mstar', 1.0)

        # Surface density of total gas - [g/cm^2]. Different ways to specify:
        #
        #   1 - Self-similar solution (Lynden-Bell & Pringle 1974). This is the
        #       default mode. The normalization can be given either as third of
        #       the surface density at `rval0` through `sigm0`, or with a disk
        #       mass through `mdisk`. Note: if `mdisk` is given, this will
        #       overwrite the `sigm0` value.
        #
        #   2 - Presscribed surface density. This allows models to be easily
        #       modelled. The input needs to be an array so that it can be
        #       interpolated between. It is assumed that the array linearlly
        #       samples the radius between `rmin` and `rmax`. If this is used
        #       then `sigm0` and `sigmq` have no meaning, while `mdisk` is
        #       calculated afterwards.
        #
        # Gaps in the disk can also be specified with `gaps`. This should be a
        # list of gap descriptions: [center, width, depth] where the centre and
        # width are in [au] and the depth relative to the unperturbed surface
        # density at the gap center. If gaps are specified, `mdisk` will be
        # recalculated.
        #
        # TODO: allow the disk mass to remain constant after gaps are added.

        self.sigm0 = kwargs.get('sigm0', 15.)
        self.mdisk = kwargs.get('mdisk', None)
        self.sigma = kwargs.get('sigma', None)
        self.sigmq = kwargs.get('sigmq', kwargs.get('gamma', 1.))

        if self.sigma is not None:
            self.sigma = self._make_profile(self.sigma)
        elif self.mdisk is not None:
            q = (2. - self.sigmq)
            self.sigm0 = self.mdisk * q * self.msun * 1e3
            self.sigm0 /= 2. * np.pi * np.power(self.rval0 * sc.au * 1e2, 2)
            self.sigm0 *= np.exp(np.power(self.rvals[0] / self.rval0, q))
            self.sigma = self._calc_surfacedensity()
        else:
            q = (2. - self.sigmq)
            self.mdisk = self.sigm0 / q / self.msun / 1e3
            self.mdisk *= 2. * np.pi * np.power(self.rval0 * sc.au * 1e2, 2)
            self.mdisk *= np.exp(np.power(self.rvals[0] / self.rval0, q))
            self.sigma = self._calc_surfacedensity()
        self.mdisk = self._calc_mdisk()

        # Include the gap perturbations. This is similar to the f(R) value in
        # Eqn. 12 of van Boekel et al. (2017). If no gaps are specified, this
        # is simply an array of ones.

        self.gaps = kwargs.get('gaps', kwargs.get('gap', None))
        self.depletion_profile = self._calc_depletion_profile(self.gaps)
        self.sigma = self.sigma * self.depletion_profile
        self.mdisk = self._calc_mdisk()

        # Temperature - two layer approximation from Dartois et al. (2013).
        # The {Htmp0, Htmpq} values describe the transition between the
        # midplane and atmospheric temperature regime. By default, this will
        # use Zq = 4Hp, where Hp is the pressure scale height.

        self.tmid0 = kwargs.get('tmid0', 20.)
        self.tatm0 = kwargs.get('tatm0', 90.)
        self.tmidq = kwargs.get('tmidq', -0.3)
        self.tatmq = kwargs.get('tatmq', -0.3)
        self.delta = kwargs.get('delta', 2.0)
        self.Htmp0 = kwargs.get('Htmp0', None)
        self.Htmpq = kwargs.get('Htmpq', None)

        # Volume density - by default the vertical structure is hydrostatic.
        # The {Hdns0, Hdnsq} parameters descibe the density scale height. If
        # dfunc='gaussian' then this will set the scaleheight used for that.
        # If none are specified, the pressure scale height is used by deafult.
        # A minimum density can be set, useful for trimming large models.

        self.Hdns0 = kwargs.get('Hdns0', None)
        self.Hdnsq = kwargs.get('Hdnsq', None)
        self.minH2 = kwargs.get('minH2', 1.e3)

        flag = False
        if self.Htmp0 is not None and self.Htmpq is not None:
            flag = True
        if self.Hdns0 is not None and self.Hdnsq is not None:
            if flag:
                raise ValueError("All four scale height parameters set.")

        self.dfunc = kwargs.get('dfunc', 'hydrostatic').lower()
        if self.dfunc not in ['gaussian', 'hydrostatic']:
            raise ValueError("dfunc must be 'gaussian' or 'hydrostatic'.")
        else:
            if self.dfunc == 'gaussian':
                self._calc_volumedensity = self._calc_volumedensity_gaussian
            else:
                self._calc_volumedensity = self._calc_volumedensity_hydrostatic

        self.tmid = self._calc_midplanetemp()
        self.tatm = self._calc_atmospheretemp()
        self.temp = self._calc_temperature()

        self.dens = self._calc_volumedensity()
        self.Hp = self._calc_scaleheight()

        # CO distribution. By default assume a disk-wide abundance value.

        self.xgas = self._make_profile(kwargs.get('xgas', 1e-4))
        self.xdep = self._make_profile(kwargs.get('xdep', self.xgas * 1e-6))
        self.column = kwargs.get('column', None)
        self.ndiss = kwargs.get('ndiss', 1.3e21)
        self.tfreeze = kwargs.get('tfreeze', 19.)

        if self.column is not None:
            self.column = self._make_profile(self.column)
            if self.verbose and all(self.xgas != 1e-4):
                print("Warning: column density will overwrite abundance.")

        self.abun = self._calc_abundance()
        self.column = self._calc_columndensity()

        # Radiative transfer.

        self.molecule = kwargs.get('molecule', 'CO').lower()
        self.rates = ratefile('%s%s.dat' % (self.rates_path, self.molecule))
        self.vturb = kwargs.get('vturb', 0.0)

        # Rotation velocity - by default just Keplerian rotation including the
        # height above the midplane. Optionally can include the disk mass in
        # the central mass calculation, or the pressure support.

        self.incl_altitude = kwargs.get('incl_altitude', True)
        self.incl_pressure = kwargs.get('incl_pressure', True)
        self.incl_diskmass = kwargs.get('incl_diskmass', False)
        self.rotation = self._calc_rotation()
        return
Пример #6
0
 def read_molecular_weight(self):
     """Read the molecular weight from collisional rates."""
     mol = self.filename.split('_')[0]
     path = os.getenv('PYTHONPATH') + '/limepy/aux/'
     rates = ratefile(path+mol+'.dat')
     return rates.mu
Пример #7
0
    def __init__(self, header, rates, **kwargs):

        self.path = os.path.dirname(__file__)
        self.aux = self.path.replace('model', 'aux/')
        self.directory = kwargs.get('directory', '../')

        # Check whether the required files are there. If so, move them.

        if not os.path.isfile('../'+header):
            raise ValueError('No header file found.')
        else:
            os.system('cp ../%s .' % header)
            self.header = readheader(header)

        if not os.path.isfile(self.aux+rates):
            raise ValueError('No collisional rates found.')
        else:
            os.system('cp %s%s .' % (self.aux, rates))
            self.moldatfile = rates
            self.rates = ratefile(rates)

        self.dust = kwargs.get('dust', None)
        if self.dust is not None:
            if not os.path.isfile(self.aux+self.dust):
                raise ValueError('No dust opacities found.')
            os.system('cp %s%s .' % (self.aux, self.dust))

        # Extract information from the header file.
        # There is the minimum 5 columns while other are optional.

        self.c1arr = self.header.params['c1arr']
        self.c2arr = self.header.params['c2arr']
        self.dens = self.header.params['dens']
        self.temp = self.header.params['temp']
        self.abund = self.header.params['abund']

        try:
            self.c3arr = self.header.params['c3arr']
        except:
            self.c3arr = None

        # Dust temperature can either equal to the gas temperature, the default
        # option, a gridded property through the 'dtemp' array, or a disk-wide
        # rescaling of the gas temperature by using the 'dtemp' value.

        try:
            self.dtemp = self.header.params['dtemp']
        except:
            self.dtemp = kwargs.get('dtemp', 1.0)

        try:
            self.g2d = self.header.params['g2d']
        except:
            self.g2d = kwargs.get('g2d', 100.)

        # Turbulence can either be specified through a single number which is
        # taken to be a disk-wide value, or through an array in the header file
        # with the name 'turb'. With either method, a type has to be specified
        # through 'turbtype' which must be either 'absolute', meaning the value
        # is in [m/s], or 'mach', so that it is a fraction of the local
        # soundspeed, the default being 'mach'. Note that the value in the
        # header will override any value entered manually.

        try:
            self.turb = self.header.params['turb']
        except:
            self.turb = kwargs.get('turb', 0.0)
        self.turbtype = kwargs.get('turbtype', 'absolute')
        if self.turbtype not in ['absolute', 'mach']:
            raise ValueError()

        # For the rotation of the disk, we first check for the 'vrot' parameter
        # in the header file. If nothing is found we revert to cylindrical
        # Keplerian rotation. If 'mstar' is provided in addition to 'vrot' in
        # the header file, we default to Keplerian rotation.

        self.vrot = self.header.params['vrot']
        if self.vrot is not None:
            self.mstar = kwargs.get('mstar', None)
        else:
            self.mstar = kwargs.get('mstar', 0.6)

        # Extract values from the header to derive properties for LIME.

        self.radius = self.header.rmax
        self.minScale = max(self.header.rmin, 1e-4)
        if self.minScale >= self.radius:
            raise ValueError('radius < minScale')

        self.ndim = self.header.ndim
        self.ncells = self.header.ncells
        self.coordsys = self.header.coordsys

        # Collisional rates. Can try to force the use of H2 over oH2 and pH2.
        # The ortho / para ratio must be iterable for the make model file and
        # is changed to the rescaling factor, e.g. for an ortho/para ratio of
        # 3, opr = [0.75, 0.25].

        self.H2 = 'H2' in self.rates.partners
        self.oH2 = 'oH2' in self.rates.partners
        self.pH2 = 'pH2' in self.rates.partners
        self.opr = kwargs.get('opr', 3.)

        if kwargs.get('onlyH2', False) and self.H2:
            self.oH2 = False
            self.pH2 = False

        if (self.oH2 and self.pH2):
            print 'Using oH2 and pH2 as collision partners.'
            self.rescaledens = np.array([self.opr, 1.]) / (1. + self.opr)
            self.collpartIds = [3, 2]
        elif self.H2:
            print 'Using H2 as single collision partner.'
            self.rescaledens = [1.0]
            self.collpartIds = [1]
        else:
            raise ValueError()

        self.nMolWeights = np.array([1.0 for cId in self.collpartIds])
        self.dustWeights = np.array([1.0 for cId in self.collpartIds])

        # Model parameters. These should affect the running of the model.

        self.pIntensity = float(kwargs.get('pIntensity', 1e4))
        self.sinkPoints = float(kwargs.get('sinkPoints', 3e3))
        if self.sinkPoints > self.pIntensity:
            print("Warning: sinkPoints > pIntensity.")

        self.samplingAlgorithm = int(kwargs.get('samplingAlgorithm', 0))
        if self.samplingAlgorithm not in [0, 1]:
            raise ValueError('samplingAlgorithm must be 0 or 1.')
        self.sampling = int(kwargs.get('sampling', 2))
        if self.sampling not in [0, 1, 2]:
            raise ValueError('sampling must be 0, 1 or 2.')

        # The gridOutFile must be boolean.
        self.gridOutFile = kwargs.get('gridOutFile', False)
        if type(self.gridOutFile) is not bool:
            raise TypeError('gridOutFile must be True / False.')

        self.lte_only = int(kwargs.get('lte_only', 1))
        if self.lte_only > 1:
            raise ValueError('lte_only must be 0 or 1.')
        elif self.lte_only == 0:
            print 'Running non-LTE model. Will be slow.'

        self.blend = int(kwargs.get('blend', 0))
        if self.blend > 1:
            raise ValueError('blend must be 0 or 1.')
        elif self.blend == 1:
            print 'Including line blending. Will be slow.'

        self.traceRayAlgorithm = int(kwargs.get('traceRayAlgorithm', 0))
        if self.traceRayAlgorithm > 1:
            raise ValueError('traceRayAlgorithm must be 0 or 1.')

        self.antialias = int(kwargs.get('antialias', 1))
        self.nSolveIters = kwargs.get('nSolveIters', None)
        self.nThreads = int(kwargs.get('nThreads', 20))

        # Image parameters. Inclinations, position angles, azimuthal angle and
        # transitions can be iterable. All permutations will be run.

        self.nchan = int(kwargs.get('nchan', 100))
        self.velres = float(kwargs.get('velres', 200.))
        self.pxls = int(kwargs.get('pxls', kwargs.get('npix', 128)))
        self.distance = float(kwargs.get('distance', kwargs.get('dist', 1.)))
        self.source_vel = kwargs.get('source_vel', 0.0)
        self.imgres = float(kwargs.get('imgres', 0))
        if self.imgres == 0.0:
            self.imgres = 2. * self.radius / self.distance / self.pxls
        self.unit = int(kwargs.get('unit', 0))
        if self.unit not in [0, 1, 2, 3]:
            raise ValueError('unit must be 0, 1, 2 or 3.')

        # Name of the final output file.

        self.name = kwargs.get('name', header)
        self.name = self.name.split('/')[-1]
        self.name = self.name.replace('.h', '')

        self.transitions = kwargs.get('transitions', [0])
        self.transitions = checkiterable(self.transitions)
        self.ntra = len(self.transitions)

        # Viewing geometry.

        self.incl = kwargs.get('incl', kwargs.get('inc', [0.]))
        self.incl = checkiterable(self.incl)
        self.ninc = len(self.incl)

        self.posang = kwargs.get('posang', kwargs.get('pa', [0.]))
        self.posang = checkiterable(self.posang)
        self.npos = len(self.posang)

        self.azimuth = kwargs.get('azimuth', [0.])
        self.azimuth = checkiterable(self.azimuth)
        self.nazi = len(self.azimuth)

        # Additional variables.

        self.cleanup = kwargs.get('cleanup', True)
        self.nmodels = int(kwargs.get('nmodels', 1))
        self.returnnoise = kwargs.get('returnnoise', False)
        if self.returnnoise and self.nmodels == 1:
            self.returnnoise = 1
            print("Only one model run, no noise will be returned.")
        self.rescaletemp = kwargs.get('rescaletemp', False)
        self.depletion = float(kwargs.get('depletion', False))

        # Imaging corrections based on Rosenfeld et al. (2013).
        # Note that if oversample is used, will vastly increase time.

        self.oversample = int(kwargs.get('oversample', 1))
        if self.oversample > 1:
            print("Increasing velocity sampling by %d." % self.oversample)
            print("Will therefore take longer than usual to ray-trace.")
        self.hanning = kwargs.get('hanning', False)
        if self.hanning:
            print("Hanning smoothing will be included.")
        self.niceness = kwargs.get('niceness', False)
        self.waittime = kwargs.get('waittime', kwargs.get('wait', 60.))

        # Additional variables to be updated.

        self.tcmb = kwargs.get('tcmb', 2.73)

        # Remove the option for continuum only observations.

        self.freq = kwargs.get('freq', None)
        if self.freq is not None:
            raise NotImplementedError()

        self.bandwidth = kwargs.get('bandwidth', None)
        if self.bandwidth is not None:
            raise NotImplementedError()

        self.gridInFile = kwargs.get('gridInFile', None)
        if self.gridInFile is not None:
            raise NotImplementedError()

        self.gridDensity = kwargs.get('gridDensity', None)
        if self.gridDensity is not None:
            raise NotImplementedError()
        return