コード例 #1
0
ファイル: NL99.py プロジェクト: hongliliu/despotic
 def AV(self, value):
     if self.cloud is None:
         if self.info is None:
             self._AV = value
         elif 'AV' in self.info:
             self.info['AV'] = value
         else:
             self._AV = value
     else:
         if self.info is None:
             raise despoticError(
                 "cannot set AV directly unless it is part of info")
         elif 'AV' not in self.info:
             raise despoticError(
                 "cannot set AV directly unless it is part of info")
         else:
             self.info['AV'] = value
コード例 #2
0
ファイル: chemNetwork.py プロジェクト: keflavich/despotic
    def dxdt(self, xin, time):
        """
        This routine returns the time rate of change of the abundances
        for all species in the network.

        Parameters
           xin : array
              array of starting abundances
           time : float
              current time in sec

        Returns
           dxdt : array
              the time derivative of all species abundances
        """
        raise despoticError(
            "chemNetwork is an abstract class, " + 
            "and should never be instantiated directly. Only " + 
            "instantiate classes derived from it.")
コード例 #3
0
ファイル: chemNetwork.py プロジェクト: hongliliu/despotic
    def dxdt(self, xin, time):
        """
        This routine returns the time rate of change of the abundances
        for all species in the network.

        Parameters
           xin : array
              array of starting abundances
           time : float
              current time in sec

        Returns
           dxdt : array
              the time derivative of all species abundances
        """
        raise despoticError(
            "chemNetwork is an abstract class, " +
            "and should never be instantiated directly. Only " +
            "instantiate classes derived from it.")
コード例 #4
0
ファイル: chemNetwork.py プロジェクト: keflavich/despotic
    def applyAbundances(self, addEmitters=False):
        """
        This method writes abundances from the chemical network back
        to the cloud to which this network is attached.

        Parameters
           addEmitters : bool
              if True, and the network contains emitters that are not
              part of the parent cloud, then the network will attempt
              to add them using cloud.addEmitter. Otherwise this
              routine will change the abundances of whatever emitters
              are already attached to the cloud, but will not add new
              ones.

        Returns:
           Nothing
        """

        raise despoticError(
            "chemNetwork is an abstract class, " + 
            "and should never be instantiated directly. Only " + 
            "instantiate classes derived from it.")
コード例 #5
0
ファイル: chemNetwork.py プロジェクト: hongliliu/despotic
    def applyAbundances(self, addEmitters=False):
        """
        This method writes abundances from the chemical network back
        to the cloud to which this network is attached.

        Parameters
           addEmitters : bool
              if True, and the network contains emitters that are not
              part of the parent cloud, then the network will attempt
              to add them using cloud.addEmitter. Otherwise this
              routine will change the abundances of whatever emitters
              are already attached to the cloud, but will not add new
              ones.

        Returns:
           Nothing
        """

        raise despoticError(
            "chemNetwork is an abstract class, " +
            "and should never be instantiated directly. Only " +
            "instantiate classes derived from it.")
コード例 #6
0
def setChemEq(cloud,
              tEqGuess=None,
              network=None,
              info=None,
              addEmitters=False,
              tol=1e-6,
              maxTime=1e16,
              verbose=False,
              smallabd=1e-15,
              convList=None,
              evolveTemp='fixed',
              isobaric=False,
              tempEqParam=None,
              dEdtParam=None,
              maxTempIter=50):
    """
    Set the chemical abundances for a cloud to their equilibrium
    values, computed using a specified chemical netowrk.

    Parameters
       cloud : class cloud
          cloud on which computation is to be performed
       tEqGuess : float
          a guess at the timescale over which equilibrium will be
          achieved; if left unspecified, the code will attempt to
          estimate this time scale on its own
       network : chemNetwork class
          a valid chemNetwork class; this class must define the
          methods __init__, dxdt, and applyAbundances; if None, the
          existing chemical network for the cloud is used
       info : dict
          a dict of additional initialization information to be passed
          to the chemical network class when it is instantiated
       addEmitters : Boolean
          if True, emitters that are included in the chemical
          network but not in the cloud's existing emitter list will
          be added; if False, abundances of emitters already in the
          emitter list will be updated, but new emiters will not be
          added to the cloud
       evolveTemp : 'fixed' | 'iterate' | 'iterateDust' | 'gasEq' | 'fullEq' | 'evol'
          how to treat the temperature evolution during the chemical
          evolution:

          * 'fixed' = treat tempeature as fixed
          * 'iterate' = iterate between setting the gas temperature and
            chemistry to equilibrium
          * 'iterateDust' = iterate between setting the gas and dust
            temperatures and the chemistry to equilibrium
          * 'gasEq' = hold dust temperature fixed, set gas temperature to
            instantaneous equilibrium value as the chemistry evolves
          * 'fullEq' = set gas and dust temperatures to instantaneous
            equilibrium values while evolving the chemistry network
          * 'evol' = evolve gas temperature in time along with the
            chemistry, assuming the dust is always in instantaneous
            equilibrium

       isobaric : Boolean
          if set to True, the gas is assumed to be isobaric during the
          evolution (constant pressure); otherwise it is assumed to be
          isochoric; note that (since chemistry networks at present are
          not allowed to change the mean molecular weight), this option
          has no effect if evolveTemp is 'fixed'
       tempEqParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.setTempEq,
          cloud.setGasTempEq, or cloud.setDustTempEq routines; only used
          if evolveTemp is not 'fixed'
       dEdtParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.dEdt
          routine; only used if evolveTemp is 'evol'
       tol : float
          tolerance requirement on the equilibrium solution
       convList : list
          list of species to include when calculating tolerances to
          decide if network is converged; species not listed are not
          considered. If this is None, then all species are considered
          in deciding if the calculation is converged.
       smallabd : float
          abundances below smallabd are not considered when checking for
          convergence; set to 0 or a negative value to consider all
          abundances, but beware that this may result in false
          non-convergence due to roundoff error in very small abundances
       maxTempIter : int
          maximum number of iterations when iterating between chemistry
          and temperature; only used if evolveTemp is 'iterate' or
          'iterateDust'
       verbose : Boolean
          if True, diagnostic information is printed as the calculation
          proceeds

    Returns
       converged : Boolean
          True if the calculation converged, False if not

    Raises
       despoticError, if network is None and the cloud does not already
       have a defined chemical network associated with it

    Remarks
       The final abundances are written to the cloud whether or not the
       calculation converges.
    """

    # Check if we have been passed a new chemical network. If so,
    # initialize it and associate it with the cloud, unless it is the
    # same type as the current network; if not, make sure the cloud
    # has a network associated with it before proceeding.
    if network is not None:
        if not hasattr(cloud, 'chemnetwork'):
            cloud.chemnetwork = network(cloud=cloud, info=info)
        elif not isinstance(cloud.chemnetwork, network):
            cloud.chemnetwork = network(cloud=cloud, info=info)
    elif not hasattr(cloud, 'chemnetwork'):
        raise despoticError('if network is None, cloud must have' +
                            ' an existing chemnetwork')

    # Get initial timesale estimate if we were not given one. Picking
    # this is very tricky, because chemical networks often involve
    # reactions with a wide range of timescales, and the rates of
    # change starting from arbitrary initial conditions may be highly
    # non-representative of those found elsewhere in parameter
    # space. To make a guess, we compute dx/dt at the initial
    # conditions and at a point slightly perturbed from them, and then
    # use the most restrictive timestep we find.
    if tEqGuess == None:

        # Print status
        if verbose:
            print("setChemEquil: estimating characteristic " +
                  "equilibration timescale...")

        # Compute current time derivatives
        xdot1 = cloud.chemnetwork.dxdt(cloud.chemnetwork.x, 0.0)
        dt1 = np.amin(
            np.abs(
                (cloud.chemnetwork.x + max(smallabd, 0)) / (xdot1 + __small)))
        x2 = cloud.chemnetwork.x + dt1 * xdot1
        xdot2 = cloud.chemnetwork.dxdt(x2, 0.0)
        dt2 = np.amin(np.abs((x2 + max(smallabd, 0)) / (xdot2 + __small)))

        # Use larger of the two xdot's to define a timescale, but
        # divide by 10 for safety, with a minimum of 10^7 sec
        tEqGuess = max(dt1, dt2)
        tEqGuess /= 10.0
        tEqGuess = max(tEqGuess, 1e7)

        # If we're evolving the gas temperature too, estimate a
        # timescale for its evolution
        if evolveTemp == 'evol':
            if dEdtParam is None:
                rates = cloud.dEdt(gasOnly=True, sumOnly=True)
            else:
                dEdtParam1 = deepcopy(dEdtParam)
                dEdtParam1['gasOnly'] = True
                dEdtParam1['sumOnly'] = True
                rates = cloud.dEdt(**dEdtParam1)
            if isobaric:
                dTdt = rates['dEdtGas'] / \
                       ((cloud.comp.computeCv(cloud.Tg)+1)*kB)
            else:
                dTdt = rates['dEdtGas'] / \
                       (cloud.comp.computeCv(cloud.Tg)*kB)
            tEqGuess = max(tEqGuess, cloud.Tg / (np.abs(dTdt) + __small))

        # Make sure tEqGuess doesn't exceed maxTime
        if tEqGuess > maxTime:
            tEqGuess = maxTime

    # Print status
    if verbose:
        print("setChemEquil: estimated equilibration timescale = " +
              str(tEqGuess) + " sec")

    # Decide which species we will consider in determining if
    # abundances are converged
    if convList == None:
        convList = cloud.chemnetwork.specList
    convArray = np.array(
        [cloud.chemnetwork.specList.index(spec) for spec in convList])

    # If we're isobaric, save the isobar
    if isobaric:
        isobar = cloud.Tg * cloud.nH

    # Outer loop, if we're iterating between temperature and chemistry
    if evolveTemp == 'iterate' or evolveTemp == 'iterateDust':
        tempConverge = False
    else:
        tempConverge = True
    itCount = 0
    while True:

        # Evolve the chemistry in time for estimated equilibrium
        # timescale and check convergence; if not converged, increase
        # time and keep running until we converge or maximum time is
        # reached.
        err = np.zeros(convArray.size) + 10.0 * tol
        t = 0.0
        tEvol = tEqGuess
        lastCycle = False
        while True:

            # Evolve for specified time
            if evolveTemp != 'iterate' and evolveTemp != 'iterateDust':
                out = chemEvol(cloud,
                               t + tEvol,
                               tInit=t,
                               nOut=3,
                               evolveTemp=evolveTemp,
                               isobaric=isobaric,
                               tempEqParam=tempEqParam,
                               dEdtParam=dEdtParam)
            else:
                out = chemEvol(cloud,
                               t + tEvol,
                               tInit=t,
                               nOut=3,
                               evolveTemp='fixed',
                               addEmitters=addEmitters)
            xOut = np.array(out[1].values())

            # Compute residual
            err = abs(xOut[convArray, -2] / (xOut[convArray, -1] + __small) -
                      1)

            # If smallabd is set, exclude species will small abundances
            # from the calculation
            if smallabd > 0.0:
                err[xOut[convArray, -1] < smallabd] = 0.1 * tol

            # Add temperature to residual if we're evolving it
            if evolveTemp == 'evol':
                TOut = xOut[2]
                err = np.append(err, abs(TOut[-2] / (TOut[-1] + __small) - 1))

            # Print status
            if verbose:
                if evolveTemp != 'evol' or np.argmax(err) < len(err - 1):
                    print(
                        "setChemEquil: evolved from t = " + str(t) + " to " +
                        str(t + tEvol) + " sec, residual = " +
                        str(np.amax(err)) + " for species " +
                        cloud.chemnetwork.specList[convArray[np.argmax(err)]])
                else:
                    print("setChemEquil: evolved from t = " + str(t) + " to " +
                          str(t + tEvol) + " sec, residual = " +
                          str(np.amax(err)) + " for temperature")

            # Check for convergence
            if np.amax(err) < tol:
                break

            # Update time and timestep, or break if we've exceed maximum
            # allowed time
            if t + tEvol < maxTime:
                t += tEvol
                tEvol *= 2.0
            else:
                if lastCycle:
                    break
                else:
                    tEvol = maxTime - t
                    lastCycle = True

        # Print status
        if verbose:
            if np.amax(err) < tol:
                ad = abundanceDict(cloud.chemnetwork.specList,
                                   cloud.chemnetwork.x)
                print("setChemEquil: abundances converged: " + str(ad))
            else:
                print("setChemEquil: reached maximum time of " + str(maxTime) +
                      " sec without converging")

        # Floor small negative abundances to avoid numerical problems
        idx = np.where(
            np.logical_and(cloud.chemnetwork.x <= 0.0,
                           np.abs(cloud.chemnetwork.x) < smallabd))
        cloud.chemnetwork.x[idx] = smallabd

        # If we failed to converge on the chemistry, bail out now
        if np.amax(err) >= tol:
            return False

        # Are we iterating on temperature?
        if not tempConverge:

            # Yes, so update temperature
            Tglast = cloud.Tg
            Tdlast = cloud.Td
            if evolveTemp == 'iterate':
                if tempEqParam is None:
                    cloud.setGasTempEq()
                else:
                    cloud.setGasTempEq(**tempEqParam)
            else:
                if tempEqParam is None:
                    cloud.setTempEq()
                else:
                    cloud.setTempEq(**tempEqParam)

            # If we're isobaric, also update the density
            if isobaric:
                cloud.nH = isobar / cloud.Tg

            # Check for temperature convergence
            resid = max(abs((cloud.Tg - Tglast) / cloud.Tg),
                        abs((cloud.Td - Tdlast) / cloud.Td))
            if resid < tol:
                tempConverge = True
            else:
                tempConverge = False

            # Print status
            if verbose:
                print(("setChemEquil: updated temperatures to " +
                       "Tg = {:f}, Td = {:f}, residual = {:e}").format(
                           cloud.Tg, cloud.Td, resid))
                if tempConverge:
                    print("Temperature converged!")

        # Break if we've also converged on the temperature
        if tempConverge:
            break

        # Update iteration counter and see if we have gone too many
        # times
        itCount += 1
        if itCount > maxTempIter:
            break

    # Write results to the cloud if we converged
    if tempConverge:
        cloud.chemnetwork.applyAbundances(addEmitters=addEmitters)

    # Report on whether we converged
    return tempConverge
コード例 #7
0
ファイル: chemEvol.py プロジェクト: keflavich/despotic
def chemEvol(cloud, tFin, tInit=0.0, nOut=100, dt=None,
             tOut=None, network=None, info=None,
             addEmitters=False, evolveTemp='fixed',
             isobaric=False, tempEqParam=None,
             dEdtParam=None):
    """
    Evolve the abundances of a cloud using the specified chemical
    network.

    Parameters
       cloud : class cloud
          cloud on which computation is to be performed
       tFin : float
          end time of integration, in sec
       tInit : float
          start time of integration, in sec
       nOut : int
          number of times at which to report the temperature; this
          is ignored if dt or tOut are set
       dt : float
          time interval between outputs, in sec; this is ignored if
          tOut is set
       tOut : array
          list of times at which to output the temperature, in s;
          must be sorted in increasing order
       network : chemical network class
          a valid chemical network class; this class must define the
          methods __init__, dxdt, and applyAbundances; if None, the
          existing chemical network for the cloud is used
       info : dict
          a dict of additional initialization information to be passed
          to the chemical network class when it is instantiated
       addEmitters : Boolean
          if True, emitters that are included in the chemical
          network but not in the cloud's existing emitter list will
          be added; if False, abundances of emitters already in the
          emitter list will be updated, but new emiters will not be
          added to the cloud
       evolveTemp : 'fixed' | 'gasEq' | 'fullEq' | 'evol'
          how to treat the temperature evolution during the chemical
          evolution; 'fixed' = treat tempeature as fixed; 'gasEq' = hold
          dust temperature fixed, set gas temperature to instantaneous
          equilibrium value; 'fullEq' = set gas and dust temperatures to
          instantaneous equilibrium values; 'evol' = evolve gas
          temperature in time along with the chemistry, assuming the
          dust is always in instantaneous equilibrium
       isobaric : Boolean
          if set to True, the gas is assumed to be isobaric during the
          evolution (constant pressure); otherwise it is assumed to be
          isochoric; note that (since chemistry networks at present are
          not allowed to change the mean molecular weight), this option
          has no effect if evolveTemp is 'fixed'
       tempEqParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.setTempEq,
          cloud.setGasTempEq, or cloud.setDustTempEq routines; only used
          if evolveTemp is not 'fixed'
       dEdtParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.dEdt
          routine; only used if evolveTemp is 'evol'

    Returns
       time : array
          array of output times, in sec
       abundances : class abundanceDict
          an abundanceDict giving the abundances as a function of time
       Tg : array
          gas temperature as a function of time; returned only if
          evolveTemp is not 'fixed'
       Td : array
          dust temperature as a function of time; returned only if
          evolveTemp is not 'fixed' or 'gasEq'

    Raises
       despoticError, if network is None and the cloud does not already
       have a defined chemical network associated with it
    """

    # Check if we have been passed a new chemical network. If so,
    # initialize it and associate it with the cloud, unless it is the
    # same type as the current network; if not, make sure the cloud
    # has a network associated with it before proceeding.
    if network is not None:
        if not hasattr(cloud, 'chemnetwork'):
            cloud.chemnetwork = network(cloud=cloud, info=info)
        elif not isinstance(cloud.chemnetwork, network):
            cloud.chemnetwork = network(cloud=cloud, info=info)
    elif not hasattr(cloud, 'chemnetwork'):
        raise despoticError(
            'if network is None, cloud must have' +
            ' an existing chemnetwork')

    # Set up output times
    if tOut==None:
        if dt==None:
            tOut = tInit + np.arange(nOut+1)*float(tFin-tInit)/nOut
        else:
            tOut = np.arange(tInit, (tFin-tInit)*(1+1e-10), dt)

    # Sanity check on output times: eliminate any output times
    # that are not between tInit and tFin, and make sure final time is
    # tFin
    tOut1 = tOut[tOut >= tInit]
    tOut1 = tOut1[tOut1 <= tFin]
    if tOut1[-1] < tFin:
        tOut1 = np.append(tOut1, tFin)

    # Set the isobar
    if isobaric:
        isobar = cloud.Tg * cloud.nH
    else:
        isobar = -1

    # See how we're handling the temperature
    if evolveTemp == 'fixed':

        # Simplest case: fixed temperature, so just evolve the
        # chemical network alone
        xOut = odeint(cloud.chemnetwork.dxdt, cloud.chemnetwork.x,
                      tOut1)

    elif evolveTemp == 'gasEq':

        # We're evolving the temperature as well as the chemical
        # abundances, but we're doing so assuming the temperature is
        # always in equilibrium; we therefore use our wrapper class,
        # defined below
        dxdtwrap = _dxdt_wrapper(cloud, isobar, gasOnly=True, 
                                 tempEqParam=tempEqParam)
        xOut = odeint(dxdtwrap.dxdt_Teq, cloud.chemnetwork.x, tOut1)

        # Go back and compute the equilibrium gas temperature at each
        # of the requested output times
        Tg = np.zeros(len(tOut1))
        for i in range(Tg.size):
            cloud.chemnetwork.x = xOut[i,:]
            cloud.chemnetwork.applyAbundances()
            if tempEqParam is not None:
                cloud.setGasTempEq(**tempEqParam)
            else:
                cloud.setGasTempEq()
            Tg[i] = cloud.Tg

    elif evolveTemp == 'fullEq':

        # Same as the gasEq case, but now we set but the gas and dust
        # temperature to equilibrium
        dxdtwrap = _dxdt_wrapper(cloud, isobar, gasOnly=False,
                                 tempEqParam=tempEqParam)
        xOut = odeint(dxdtwrap.dxdt_Teq, cloud.chemnetwork.x, tOut1)

        # Go back and compute the equilibrium gas and dust
        # temperatures at each of the requested output times
        Tg = np.zeros(len(tOut1))
        Td = np.zeros(len(tOut1))
        for i in range(Tg.size):
            cloud.chemnetwork.x = xOut[i,:]
            cloud.chemnetwork.applyAbundances()
            if tempEqParam is not None:
                cloud.setTempEq(**tempEqParam)
            else:
                cloud.setTempEq()
            Tg[i] = cloud.Tg
            Td[i] = cloud.Td

    elif evolveTemp == 'evol':

        # Evolve the chemistry, gas, and dust temperatures
        # simultaneously
        dxdtwrap = _dxdt_wrapper(cloud, isobar,
                                 tempEqParam=tempEqParam,
                                 dEdtParam=dEdtParam)
        xTInit = np.append(cloud.chemnetwork.x, cloud.Tg)
        xTOut = odeint(dxdtwrap.dxTdt, xTInit, tOut1)
        xOut = xTOut[:,:-1]
        Tg = xTOut[:,-1]

        # Go back and compute equilibrium dust temperatures at each
        # output time
        Td = np.zeros(Tg.size)
        for i in range(Tg.size):
            cloud.chemnetwork.x = xOut[i,:]
            cloud.chemnetwork.applyAbundances()
            if tempEqParam is not None:
                cloud.setDustTempEq(**tempEqParam)
            else:
                cloud.setDustTempEq()
            Td[i] = cloud.Td

    else:
        raise despoticError(
            'chemEvol: invalid option ' + str(evolveTemp) +
            'for evolveTemp')

    # Write final results to chemnetwork
    cloud.chemnetwork.x = xOut[-1,:]

    # Write final abundances back to cloud
    cloud.chemnetwork.applyAbundances(addEmitters=addEmitters)

    # If the final time was not one of the requested output times,
    # chop it off the data to be returned
    if np.sum(tFin == tOut):
        xOut = xOut[:-1,:]
        if evolveTemp != 'fixed':
            Tg = Tg[:-1]
        if evolveTemp == 'evol' or evolveTemp == 'fullEq':
            Td = Td[:-1]

    # Return output
    if evolveTemp == 'fixed':
        return tOut, abundanceDict(cloud.chemnetwork.specList,
                                   np.transpose(xOut)), 
    elif evolveTemp == 'gasEq':
        return tOut, abundanceDict(cloud.chemnetwork.specList,
                                   np.transpose(xOut)), Tg
    else:
        return tOut, abundanceDict(cloud.chemnetwork.specList,
                                   np.transpose(xOut)), Tg, Td
コード例 #8
0
ファイル: NL99.py プロジェクト: hongliliu/despotic
 def cfac(self, value):
     raise despoticError(
         "cannot set cfac directly; set sigmaNT or temp instead")
コード例 #9
0
ファイル: NL99.py プロジェクト: hongliliu/despotic
    def __init__(self, cloud=None, info=None):
        """
        Parameters
           cloud : class cloud
              a DESPOTIC cloud object from which initial data are to be
              taken
           info : dict
              a dict containing additional parameters

        Remarks
           The dict info may contain the following key - value pairs:

           'xC' : float
              the total C abundance per H nucleus; defaults to 2.0e-4
           'xO' : float
              the total H abundance per H nucleus;
              defaults to 4.0e-4
           'xM' : float
              the total refractory metal abundance per H
              nucleus; defaults to 2.0e-7
           'sigmaDustV' : float
              V band dust extinction cross
              section per H nucleus; if not set, the default behavior
              is to assume that sigmaDustV = 0.4 * cloud.dust.sigmaPE
           'AV' : float
              total visual extinction; ignored if
              sigmaDustV is set
           'noClump' : bool
              if True, the clump factor is set to
              1.0; defaults to False
        """

        # List of species for this network; provide a pointer here so
        # that it can be accessed through the class
        self.specList = specList
        self.specListExtended = specListExtended

        # Store the input info dict
        self.info = info

        # Array to hold abundances
        self.x = np.zeros(10)

        # Total metal abundance
        if info is None:
            self.xM = _xMdefault
        else:
            if 'xM' in info:
                self.xM = info['xM']
            else:
                self.xM = _xMdefault

        # Extract information from the cloud if one is given
        if cloud is None:

            # No cloud given, so set some defaults
            self.cloud = None

            # Physical properties
            self._xHe = _xHedefault
            self._ionRate = 2.0e-17
            self._NH = _small
            self._temp = _small
            self._chi = 1.0
            self._nH = _small
            self._AV = 0.0
            if info is not None:
                if 'AV' in info:
                    self._AV = info['AV']

            # Set initial abundances
            if info is None:
                self.x[6] = _xCdefault
                self.x[8] = _xOdefault
            else:
                if 'xC' in info:
                    self.x[6] = info['xC']
                else:
                    self.x[6] = _xCdefault
                if 'xO' in info:
                    self.x[8] = info['xO']
                else:
                    self.x[8] = _xOdefault
            self.x[9] = self.xM

        else:

            # Cloud is given, so get information out of it
            self.cloud = cloud

            # Sanity check: make sure cloud is pure H2
            if cloud.comp.xH2 != 0.5:
                raise despoticError(
                    "NL99 network only valid " + 
                    "for pure H2 composition")

            # Sanity check: make sure cloud contains some He, since
            # network will not function properly at He abundance of 0
            if cloud.comp.xHe == 0.0:
                raise despoticError(
                    "NL99 network requires " + 
                    "non-zero He abundance")

            # Set abundances

            # Make a case-insensitive version of the emitter list for
            # convenience
            try:
                emList = dict(zip(map(string.lower, 
                                      cloud.emitters.keys()), 
                                  cloud.emitters.values()))
            except:
                # This somewhat more bulky construction is required in
                # python 3
                lowkeys = [k.lower() for k in cloud.emitters.keys()]
                lowvalues = list(cloud.emitters.values())
                emList = dict(zip(lowkeys, lowvalues))

            # OH and H2O
            if 'oh' in emList:
                self.x[2] += emList['oh'].abundance
            if 'ph2o' in emList:
                self.x[2] += emList['ph2o'].abundance
            if 'oh2o' in emList:
                self.x[2] += emList['oh2o'].abundance
            if 'p-h2o' in emList:
                self.x[2] += emList['p-h2o'].abundance
            if 'o-h2o' in emList:
                self.x[2] += emList['o-h2o'].abundance

            # CO
            if 'co' in emList:
                self.x[4] = emList['co'].abundance

            # Neutral carbon
            if 'c' in emList:
                self.x[5] = emList['c'].abundance

            # Ionized carbon
            if 'c+' in emList:
                self.x[6] = emList['c+'].abundance

            # HCO+
            if 'hco+' in emList:
                self.x[7] = emList['hco+'].abundance

            # Sum input abundances of C, C+, CO, HCO+ to ensure that
            # all carbon is accounted for. If there is too little,
            # assume the excess is C+. If there is too much, throw an
            # error.
            if info is None:
                xC = _xCdefault
            elif 'xC' in info:
                xC = info['xC']
            else:
                xC = _xCdefault
            xCtot = self.x[4] + self.x[5] + self.x[6] + self.x[7]
            if xCtot < xC:
                # Print warning if we're altering existing C+
                # abundance.
                if 'c' in emList:
                    print("Warning: input C abundance is " + 
                        str(xC) + ", but total input C, C+, CHx, CO, " + 
                        "HCO+ abundance is " + str(xCtot) + 
                        "; increasing xC+ to " + str(self.x[6]+xC-xCtot))
                self.x[6] += xC - xCtot
            elif xCtot > xC:
                # Throw an error if input C abundance is smaller than
                # what is accounted for in initial conditions
                raise despoticError(
                    "input C abundance is " + 
                    str(xC) + ", but total input C, C+, CHx, CO, " + 
                    "HCO+ abundance is " + str(xCtot))

            # O
            if 'o' in emList:
                self.x[8] = emList['o'].abundance
            elif info is None:
                self.x[8] = _xOdefault - self.x[2] - self.x[4] - \
                    self.x[7]
            elif 'xO' in info:
                self.x[8] = info['xO'] - self.x[2] - self.x[4] - \
                    self.x[7]
            else:
                self.x[8] = _xOdefault - self.x[2] - self.x[4] - \
                    self.x[7]

            # As with C, make sure all O is accounted for, and if not
            # park the extra in OI
            if info is None:
                xO = _xOdefault
            elif 'xC' in info:
                xO = info['xO']
            else:
                xO = _xOdefault
            xOtot = self.x[2] + self.x[4] + self.x[7] + self.x[8]
            if xOtot < xO:
                # Print warning if we're altering existing O
                # abundance.
                if 'o' in emList:
                    print("Warning: input O abundance is " + 
                          str(xO) + ", but total input O, OHx, CO, " + 
                          "HCO+ abundance is " + str(xOtot) + 
                          "; increasing xO to " + str(self.x[8]+xO-xOtot))
                self.x[8] += xO - xOtot
            elif xOtot > xO:
                # Throw an error if input O abundance is smaller than
                # what is accounted for in initial conditions
                raise despoticError(
                    "input C abundance is " + 
                    str(xO) + ", but total input O, OHx, CO, " + 
                    "HCO+ abundance is " + str(xOtot))


        # Initial electrons = metals + C+ + HCO+
        xeinit = self.xM + self.x[6] + self.x[7]

        # Initial He+
        self.x[0] = self.xHe*self.ionRate / \
            (self.nH*(_k2[9]*self.temp**_k2Texp[9]*xeinit+_k2[3]*_xH2))

        # Initial H3+
        self.x[1] = _xH2*self.ionRate / \
            (self.nH*(_k2[10]*self.temp**_k2Texp[10]*xeinit+_k2[2]*self.x[8]))

        # Initial M+
        self.x[9] = self.xM
コード例 #10
0
ファイル: chemNetwork.py プロジェクト: keflavich/despotic
 def __init__(self, cloud=None, info=None):
     raise despoticError(
         "chemNetwork is an abstract class, " + 
         "and should never be instantiated directly. Only " + 
         "instantiate classes derived from it.")
コード例 #11
0
def chemEvol(cloud,
             tFin,
             tInit=0.0,
             nOut=100,
             dt=None,
             tOut=None,
             network=None,
             info=None,
             addEmitters=False,
             evolveTemp='fixed',
             isobaric=False,
             tempEqParam=None,
             dEdtParam=None):
    """
    Evolve the abundances of a cloud using the specified chemical
    network.

    Parameters
       cloud : class cloud
          cloud on which computation is to be performed
       tFin : float
          end time of integration, in sec
       tInit : float
          start time of integration, in sec
       nOut : int
          number of times at which to report the temperature; this
          is ignored if dt or tOut are set
       dt : float
          time interval between outputs, in sec; this is ignored if
          tOut is set
       tOut : array
          list of times at which to output the temperature, in s;
          must be sorted in increasing order
       network : chemical network class
          a valid chemical network class; this class must define the
          methods __init__, dxdt, and applyAbundances; if None, the
          existing chemical network for the cloud is used
       info : dict
          a dict of additional initialization information to be passed
          to the chemical network class when it is instantiated
       addEmitters : Boolean
          if True, emitters that are included in the chemical
          network but not in the cloud's existing emitter list will
          be added; if False, abundances of emitters already in the
          emitter list will be updated, but new emiters will not be
          added to the cloud
       evolveTemp : 'fixed' | 'gasEq' | 'fullEq' | 'evol'
          how to treat the temperature evolution during the chemical
          evolution; 'fixed' = treat tempeature as fixed; 'gasEq' = hold
          dust temperature fixed, set gas temperature to instantaneous
          equilibrium value; 'fullEq' = set gas and dust temperatures to
          instantaneous equilibrium values; 'evol' = evolve gas
          temperature in time along with the chemistry, assuming the
          dust is always in instantaneous equilibrium
       isobaric : Boolean
          if set to True, the gas is assumed to be isobaric during the
          evolution (constant pressure); otherwise it is assumed to be
          isochoric; note that (since chemistry networks at present are
          not allowed to change the mean molecular weight), this option
          has no effect if evolveTemp is 'fixed'
       tempEqParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.setTempEq,
          cloud.setGasTempEq, or cloud.setDustTempEq routines; only used
          if evolveTemp is not 'fixed'
       dEdtParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.dEdt
          routine; only used if evolveTemp is 'evol'

    Returns
       time : array
          array of output times, in sec
       abundances : class abundanceDict
          an abundanceDict giving the abundances as a function of time
       Tg : array
          gas temperature as a function of time; returned only if
          evolveTemp is not 'fixed'
       Td : array
          dust temperature as a function of time; returned only if
          evolveTemp is not 'fixed' or 'gasEq'

    Raises
       despoticError, if network is None and the cloud does not already
       have a defined chemical network associated with it
    """

    # Check if we have been passed a new chemical network. If so,
    # initialize it and associate it with the cloud, unless it is the
    # same type as the current network; if not, make sure the cloud
    # has a network associated with it before proceeding.
    if network is not None:
        if not hasattr(cloud, 'chemnetwork'):
            cloud.chemnetwork = network(cloud=cloud, info=info)
        elif not isinstance(cloud.chemnetwork, network):
            cloud.chemnetwork = network(cloud=cloud, info=info)
    elif not hasattr(cloud, 'chemnetwork'):
        raise despoticError('if network is None, cloud must have' +
                            ' an existing chemnetwork')

    # Set up output times
    if tOut == None:
        if dt == None:
            tOut = tInit + np.arange(nOut + 1) * float(tFin - tInit) / nOut
        else:
            tOut = np.arange(tInit, (tFin - tInit) * (1 + 1e-10), dt)

    # Sanity check on output times: eliminate any output times
    # that are not between tInit and tFin, and make sure final time is
    # tFin
    tOut1 = tOut[tOut >= tInit]
    tOut1 = tOut1[tOut1 <= tFin]
    if tOut1[-1] < tFin:
        tOut1 = np.append(tOut1, tFin)

    # Set the isobar
    if isobaric:
        isobar = cloud.Tg * cloud.nH
    else:
        isobar = -1

    # See how we're handling the temperature
    if evolveTemp == 'fixed':

        # Simplest case: fixed temperature, so just evolve the
        # chemical network alone
        xOut = odeint(cloud.chemnetwork.dxdt, cloud.chemnetwork.x, tOut1)

    elif evolveTemp == 'gasEq':

        # We're evolving the temperature as well as the chemical
        # abundances, but we're doing so assuming the temperature is
        # always in equilibrium; we therefore use our wrapper class,
        # defined below
        dxdtwrap = _dxdt_wrapper(cloud,
                                 isobar,
                                 gasOnly=True,
                                 tempEqParam=tempEqParam)
        xOut = odeint(dxdtwrap.dxdt_Teq, cloud.chemnetwork.x, tOut1)

        # Go back and compute the equilibrium gas temperature at each
        # of the requested output times
        Tg = np.zeros(len(tOut1))
        for i in range(Tg.size):
            cloud.chemnetwork.x = xOut[i, :]
            cloud.chemnetwork.applyAbundances()
            if tempEqParam is not None:
                cloud.setGasTempEq(**tempEqParam)
            else:
                cloud.setGasTempEq()
            Tg[i] = cloud.Tg

    elif evolveTemp == 'fullEq':

        # Same as the gasEq case, but now we set but the gas and dust
        # temperature to equilibrium
        dxdtwrap = _dxdt_wrapper(cloud,
                                 isobar,
                                 gasOnly=False,
                                 tempEqParam=tempEqParam)
        xOut = odeint(dxdtwrap.dxdt_Teq, cloud.chemnetwork.x, tOut1)

        # Go back and compute the equilibrium gas and dust
        # temperatures at each of the requested output times
        Tg = np.zeros(len(tOut1))
        Td = np.zeros(len(tOut1))
        for i in range(Tg.size):
            cloud.chemnetwork.x = xOut[i, :]
            cloud.chemnetwork.applyAbundances()
            if tempEqParam is not None:
                cloud.setTempEq(**tempEqParam)
            else:
                cloud.setTempEq()
            Tg[i] = cloud.Tg
            Td[i] = cloud.Td

    elif evolveTemp == 'evol':

        # Evolve the chemistry, gas, and dust temperatures
        # simultaneously
        dxdtwrap = _dxdt_wrapper(cloud,
                                 isobar,
                                 tempEqParam=tempEqParam,
                                 dEdtParam=dEdtParam)
        xTInit = np.append(cloud.chemnetwork.x, cloud.Tg)
        xTOut = odeint(dxdtwrap.dxTdt, xTInit, tOut1)
        xOut = xTOut[:, :-1]
        Tg = xTOut[:, -1]

        # Go back and compute equilibrium dust temperatures at each
        # output time
        Td = np.zeros(Tg.size)
        for i in range(Tg.size):
            cloud.chemnetwork.x = xOut[i, :]
            cloud.chemnetwork.applyAbundances()
            if tempEqParam is not None:
                cloud.setDustTempEq(**tempEqParam)
            else:
                cloud.setDustTempEq()
            Td[i] = cloud.Td

    else:
        raise despoticError('chemEvol: invalid option ' + str(evolveTemp) +
                            'for evolveTemp')

    # Write final results to chemnetwork
    cloud.chemnetwork.x = xOut[-1, :]

    # Write final abundances back to cloud
    cloud.chemnetwork.applyAbundances(addEmitters=addEmitters)

    # If the final time was not one of the requested output times,
    # chop it off the data to be returned
    if np.sum(tFin == tOut):
        xOut = xOut[:-1, :]
        if evolveTemp != 'fixed':
            Tg = Tg[:-1]
        if evolveTemp == 'evol' or evolveTemp == 'fullEq':
            Td = Td[:-1]

    # Return output
    if evolveTemp == 'fixed':
        return tOut, abundanceDict(cloud.chemnetwork.specList,
                                   np.transpose(xOut)),
    elif evolveTemp == 'gasEq':
        return tOut, abundanceDict(cloud.chemnetwork.specList,
                                   np.transpose(xOut)), Tg
    else:
        return tOut, abundanceDict(cloud.chemnetwork.specList,
                                   np.transpose(xOut)), Tg, Td
コード例 #12
0
ファイル: setChemEq.py プロジェクト: keflavich/despotic
def setChemEq(cloud, tEqGuess=None, network=None, info=None,
              addEmitters=False, tol=1e-6, maxTime=1e16,
              verbose=False, smallabd=1e-15, convList=None,
              evolveTemp='fixed', isobaric=False, tempEqParam=None,
              dEdtParam=None, maxTempIter=50):
    """
    Set the chemical abundances for a cloud to their equilibrium
    values, computed using a specified chemical netowrk.

    Parameters
       cloud : class cloud
          cloud on which computation is to be performed
       tEqGuess : float
          a guess at the timescale over which equilibrium will be
          achieved; if left unspecified, the code will attempt to
          estimate this time scale on its own
       network : chemNetwork class
          a valid chemNetwork class; this class must define the
          methods __init__, dxdt, and applyAbundances; if None, the
          existing chemical network for the cloud is used
       info : dict
          a dict of additional initialization information to be passed
          to the chemical network class when it is instantiated
       addEmitters : Boolean
          if True, emitters that are included in the chemical
          network but not in the cloud's existing emitter list will
          be added; if False, abundances of emitters already in the
          emitter list will be updated, but new emiters will not be
          added to the cloud
       evolveTemp : 'fixed' | 'iterate' | 'iterateDust' | 'gasEq' | 'fullEq' | 'evol'
          how to treat the temperature evolution during the chemical
          evolution:

          * 'fixed' = treat tempeature as fixed
          * 'iterate' = iterate between setting the gas temperature and
            chemistry to equilibrium
          * 'iterateDust' = iterate between setting the gas and dust
            temperatures and the chemistry to equilibrium
          * 'gasEq' = hold dust temperature fixed, set gas temperature to
            instantaneous equilibrium value as the chemistry evolves
          * 'fullEq' = set gas and dust temperatures to instantaneous
            equilibrium values while evolving the chemistry network
          * 'evol' = evolve gas temperature in time along with the
            chemistry, assuming the dust is always in instantaneous
            equilibrium

       isobaric : Boolean
          if set to True, the gas is assumed to be isobaric during the
          evolution (constant pressure); otherwise it is assumed to be
          isochoric; note that (since chemistry networks at present are
          not allowed to change the mean molecular weight), this option
          has no effect if evolveTemp is 'fixed'
       tempEqParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.setTempEq,
          cloud.setGasTempEq, or cloud.setDustTempEq routines; only used
          if evolveTemp is not 'fixed'
       dEdtParam : None | dict
          if this is not None, then it must be a dict of values that
          will be passed as keyword arguments to the cloud.dEdt
          routine; only used if evolveTemp is 'evol'
       tol : float
          tolerance requirement on the equilibrium solution
       convList : list
          list of species to include when calculating tolerances to
          decide if network is converged; species not listed are not
          considered. If this is None, then all species are considered
          in deciding if the calculation is converged.
       smallabd : float
          abundances below smallabd are not considered when checking for
          convergence; set to 0 or a negative value to consider all
          abundances, but beware that this may result in false
          non-convergence due to roundoff error in very small abundances
       maxTempIter : int
          maximum number of iterations when iterating between chemistry
          and temperature; only used if evolveTemp is 'iterate' or
          'iterateDust'
       verbose : Boolean
          if True, diagnostic information is printed as the calculation
          proceeds

    Returns
       converged : Boolean
          True if the calculation converged, False if not

    Raises
       despoticError, if network is None and the cloud does not already
       have a defined chemical network associated with it

    Remarks
       The final abundances are written to the cloud whether or not the
       calculation converges.
    """

    # Check if we have been passed a new chemical network. If so,
    # initialize it and associate it with the cloud, unless it is the
    # same type as the current network; if not, make sure the cloud
    # has a network associated with it before proceeding.
    if network is not None:
        if not hasattr(cloud, 'chemnetwork'):
            cloud.chemnetwork = network(cloud=cloud, info=info)
        elif not isinstance(cloud.chemnetwork, network):
            cloud.chemnetwork = network(cloud=cloud, info=info)
    elif not hasattr(cloud, 'chemnetwork'):
        raise despoticError(
            'if network is None, cloud must have' +
            ' an existing chemnetwork')

    # Get initial timesale estimate if we were not given one. Picking
    # this is very tricky, because chemical networks often involve
    # reactions with a wide range of timescales, and the rates of
    # change starting from arbitrary initial conditions may be highly
    # non-representative of those found elsewhere in parameter
    # space. To make a guess, we compute dx/dt at the initial
    # conditions and at a point slightly perturbed from them, and then
    # use the most restrictive timestep we find.
    if tEqGuess == None:

        # Print status
        if verbose:
            print("setChemEquil: estimating characteristic " + 
                  "equilibration timescale...")

        # Compute current time derivatives
        xdot1 = cloud.chemnetwork.dxdt(cloud.chemnetwork.x, 0.0)
        dt1 = np.amin(np.abs((cloud.chemnetwork.x+max(smallabd,0)) /
                            (xdot1+__small)))
        x2 = cloud.chemnetwork.x + dt1*xdot1
        xdot2 = cloud.chemnetwork.dxdt(x2, 0.0)
        dt2 = np.amin(np.abs((x2+max(smallabd,0)) / (xdot2+__small)))

        # Use larger of the two xdot's to define a timescale, but
        # divide by 10 for safety, with a minimum of 10^7 sec
        tEqGuess = max(dt1, dt2)
        tEqGuess /= 10.0
        tEqGuess = max(tEqGuess, 1e7)

        # If we're evolving the gas temperature too, estimate a
        # timescale for its evolution
        if evolveTemp == 'evol':
            if dEdtParam is None:
                rates = cloud.dEdt(gasOnly=True, sumOnly=True)
            else:
                dEdtParam1 = deepcopy(dEdtParam)
                dEdtParam1['gasOnly'] = True
                dEdtParam1['sumOnly'] = True
                rates = cloud.dEdt(**dEdtParam1)
            if isobaric:
                dTdt = rates['dEdtGas'] / \
                       ((cloud.comp.computeCv(cloud.Tg)+1)*kB)
            else:
                dTdt = rates['dEdtGas'] / \
                       (cloud.comp.computeCv(cloud.Tg)*kB)
            tEqGuess = max(tEqGuess, cloud.Tg/(np.abs(dTdt)+__small))

        # Make sure tEqGuess doesn't exceed maxTime
        if tEqGuess > maxTime:
            tEqGuess = maxTime

    # Print status
    if verbose:
        print("setChemEquil: estimated equilibration timescale = " + 
              str(tEqGuess) + " sec")

    # Decide which species we will consider in determining if
    # abundances are converged
    if convList == None:
        convList = cloud.chemnetwork.specList
    convArray = np.array([cloud.chemnetwork.specList.index(spec)
                          for spec in convList])

    # If we're isobaric, save the isobar
    if isobaric:
        isobar = cloud.Tg * cloud.nH

    # Outer loop, if we're iterating between temperature and chemistry
    if evolveTemp == 'iterate' or evolveTemp == 'iterateDust':
        tempConverge = False
    else:
        tempConverge = True
    itCount = 0
    while True:

        # Evolve the chemistry in time for estimated equilibrium
        # timescale and check convergence; if not converged, increase
        # time and keep running until we converge or maximum time is
        # reached.
        err = np.zeros(convArray.size)+10.0*tol
        t = 0.0
        tEvol = tEqGuess
        lastCycle = False
        while True:

            # Evolve for specified time
            if evolveTemp != 'iterate' and evolveTemp != 'iterateDust':
                out = chemEvol(cloud, t+tEvol, tInit=t, nOut=3,
                               evolveTemp=evolveTemp, isobaric=isobaric,
                               tempEqParam=tempEqParam,
                               dEdtParam=dEdtParam)
            else:
                out = chemEvol(cloud, t+tEvol, tInit=t, nOut=3,
                               evolveTemp='fixed', 
                               addEmitters=addEmitters)
            xOut = np.array(out[1].values())

            # Compute residual
            err = abs(xOut[convArray,-2]/(xOut[convArray,-1]+__small)-1)

            # If smallabd is set, exclude species will small abundances
            # from the calculation
            if smallabd > 0.0:
                err[xOut[convArray,-1] < smallabd] = 0.1*tol

            # Add temperature to residual if we're evolving it
            if evolveTemp == 'evol':
                TOut = xOut[2]
                err = np.append(err, abs(TOut[-2]/(TOut[-1]+__small)-1))

            # Print status
            if verbose:
                if evolveTemp != 'evol' or np.argmax(err) < len(err-1):
                    print(
                        "setChemEquil: evolved from t = " + str(t) + 
                        " to "+str(t+tEvol)+" sec, residual = " + 
                        str(np.amax(err)) + " for species " + 
                        cloud.chemnetwork.specList[convArray[np.argmax(err)]])
                else:
                    print(
                        "setChemEquil: evolved from t = " + str(t) + 
                        " to "+str(t+tEvol)+" sec, residual = " + 
                        str(np.amax(err)) + " for temperature")

            # Check for convergence
            if np.amax(err) < tol:
                break

            # Update time and timestep, or break if we've exceed maximum
            # allowed time
            if t + tEvol < maxTime:
                t += tEvol
                tEvol *= 2.0
            else:
                if lastCycle:
                    break
                else:
                    tEvol = maxTime-t
                    lastCycle = True

        # Print status
        if verbose:
            if np.amax(err) < tol:
                ad = abundanceDict(cloud.chemnetwork.specList,
                                   cloud.chemnetwork.x)
                print(
                    "setChemEquil: abundances converged: " + str(ad))
            else:
                print(
                    "setChemEquil: reached maximum time of " + 
                    str(maxTime) + " sec without converging")

        # Floor small negative abundances to avoid numerical problems
        idx = np.where(np.logical_and(cloud.chemnetwork.x <= 0.0,
                                      np.abs(cloud.chemnetwork.x) < smallabd))
        cloud.chemnetwork.x[idx] = smallabd

        # If we failed to converge on the chemistry, bail out now
        if np.amax(err) >= tol:
            return False

        # Are we iterating on temperature?
        if not tempConverge:

            # Yes, so update temperature
            Tglast = cloud.Tg
            Tdlast = cloud.Td
            if evolveTemp == 'iterate':
                if tempEqParam is None:
                    cloud.setGasTempEq()
                else:
                    cloud.setGasTempEq(**tempEqParam)
            else:
                if tempEqParam is None:
                    cloud.setTempEq()
                else:
                    cloud.setTempEq(**tempEqParam)

            # If we're isobaric, also update the density
            if isobaric:
                cloud.nH = isobar / cloud.Tg

            # Check for temperature convergence
            resid = max(abs((cloud.Tg-Tglast)/cloud.Tg),
                        abs((cloud.Td-Tdlast)/cloud.Td))
            if resid < tol:
                tempConverge = True
            else:
                tempConverge = False

            # Print status
            if verbose:
                print("setChemEquil: updated temperatures to " +
                      "Tg = {:f}, Td = {:f}, residual = {:e}"). \
                    format(cloud.Tg, cloud.Td, resid)
                if tempConverge:
                    print("Temperature converged!")

        # Break if we've also converged on the temperature
        if tempConverge:
            break

        # Update iteration counter and see if we have gone too many
        # times
        itCount += 1
        if itCount > maxTempIter:
            break

    # Write results to the cloud if we converged
    if tempConverge:
        cloud.chemnetwork.applyAbundances(addEmitters=addEmitters)

    # Report on whether we converged
    return tempConverge
コード例 #13
0
ファイル: chemNetwork.py プロジェクト: hongliliu/despotic
 def __init__(self, cloud=None, info=None):
     raise despoticError(
         "chemNetwork is an abstract class, " +
         "and should never be instantiated directly. Only " +
         "instantiate classes derived from it.")
コード例 #14
0
 def cfac(self, value):
     raise despoticError("cannot set cfac directly")
コード例 #15
0
    def __init__(self, cloud=None, info=None):
        """
        Parameters
        cloud : class cloud
           a DESPOTIC cloud object from which initial data are to be
           taken
        info : dict
           a dict containing additional parameters

        Returns
           Nothing

        Remarks
           The dict info may contain the following key - value pairs:

           'xC' : float
              the total C abundance per H nucleus; defaults to 2.0e-4
           'xO' : float
              the total H abundance per H nucleus; defaults to 4.0e-4
           'xM' : float
              the total refractory metal abundance per H
              nucleus; defaults to 2.0e-7
           'Zd' : float
          dust abundance in solar units; defaults to 1.0
           'sigmaDustV' : float
              V band dust extinction cross
              section per H nucleus; if not set, the default behavior
              is to assume that sigmaDustV = 0.4 * cloud.dust.sigmaPE
           'AV' : float
              total visual extinction; ignored if sigmaDustV is set
           'noClump' : bool
              if True, the clumping factor is set to 1.0; defaults to False
           'sigmaNT' : float
              non-thermal velocity dispersion
           'temp' : float
              gas kinetic temperature
        """

        # List of species for this network; provide a pointer here so
        # that it can be accessed through the class
        self.specList = specList
        self.specListExtended = specListExtended

        # Store the input info dict
        self.info = info

        # Array to hold abundances; wrap it in an abundanceDict for
        # convenience
        self.x = np.zeros(len(specList))
        abd = abundanceDict(specList, self.x)

        # Total metal abundance
        if info is None:
            self.xM = _xMdefault
        else:
            if 'xM' in info:
                self.xM = info['xM']
            else:
                self.xM = _xMdefault

        # Extract information from the cloud if one is given
        if cloud is None:

            # No cloud given, so set some defaults
            self.cloud = None

            # Physical properties
            self._xHe = _xHedefault
            self._ionRate = 2.0e-17
            self._NH = _small
            self._temp = _small
            self._chi = 1.0
            self._nH = _small
            self._AV = 0.0
            self._sigmaNT = _small
            self._Zd = _Zddefault
            if info is not None:
                if 'AV' in info.keys():
                    self._AV = info['AV']
                if 'sigmaNT' in info.keys():
                    self._sigmaNT = info['sigmaNT']
                if 'Zd' in info.keys():
                    self._Zd = info['Zd']
                if 'Tg' in info.keys():
                    self._temp = info['Tg']
                if 'Td' in info.keys():
                    self._Td = info['Td']

            # Set initial abundances
            if info is None:
                # If not specied, start all hydrogen as H, all C as C+,
                # all O as OI
                abd['C+'] = _xCdefault
                abd['O'] = _xOdefault
            else:
                if 'xC' in info.keys():
                    abd['C+'] = info['xC']
                else:
                    abd['C+'] = _xCdefault
                if 'xO' in info.keys():
                    abd['O'] = info['xO']
                else:
                    abd['O'] = _xOdefault
            abd['M+'] = self.xM

        else:

            # Cloud is given, so get information out of it
            self.cloud = cloud

            # Sanity check: make sure cloud contains some He, since
            # network will not function properly at He abundance of 0
            if cloud.comp.xHe == 0.0:
                raise despoticError(
                    "NL99_GC network requires " + 
                    "non-zero He abundance")

            # Set abundances

            # Make a case-insensitive version of the emitter list for
            # convenience
            try:
                # This construction is elegant, but it relies on the
                # existence of a freestanding string.lower function,
                # which has been removed in python 3
                emList = dict(zip(map(string.lower, 
                                      cloud.emitters.keys()), 
                                  cloud.emitters.values()))
            except:
                # This somewhat more bulky construction is required in
                # python 3
                lowkeys = [k.lower() for k in cloud.emitters.keys()]
                lowvalues = list(cloud.emitters.values())
                emList = dict(zip(lowkeys, lowvalues))

            # Hydrogen
            abd['H+'] = cloud.comp.xHplus
            abd['H2'] = cloud.comp.xpH2 + cloud.comp.xoH2

            # OH and H2O
            if 'oh' in emList:
                abd['OHx'] += emList['oh'].abundance
            if 'ph2o' in emList:
                abd['OHx'] += emList['ph2o'].abundance
            if 'oh2o' in emList:
                abd['OHx'] += emList['oh2o'].abundance
            if 'p-h2o' in emList:
                abd['OHx'] += emList['p-h2o'].abundance
            if 'o-h2o' in emList:
                abd['OHx'] += emList['o-h2o'].abundance

            # CO
            if 'co' in emList:
                abd['CO'] = emList['co'].abundance

            # Neutral carbon
            if 'c' in emList:
                abd['C'] = emList['c'].abundance

            # Ionized carbon
            if 'c+' in emList:
                abd['C+'] = emList['c+'].abundance

            # HCO+
            if 'hco+' in emList:
                abd['HCO+'] = emList['hco+'].abundance

            # Sum input abundances of C, C+, CO, HCO+ to ensure that
            # all carbon is accounted for. If there is too little,
            # assume the excess is C+. If there is too much, throw an
            # error.
            if info is None:
                xC = _xCdefault
            elif 'xC' in info:
                xC = info['xC']
            else:
                xC = _xCdefault
            xCtot = abd['CO'] + abd['C'] + abd['C+'] + abd['HCO+']
            if xCtot < xC:
                # Print warning if we're altering existing C+
                # abundance.
                if 'c' in emList:
                    print("Warning: input C abundance is " + 
                          str(xC) + ", but total input C, C+, CHx, CO, " + 
                          "HCO+ abundance is " + str(xCtot) + 
                          "; increasing xC+ to " + str(abd['C+']+xC-xCtot))
                abd['C+'] += xC - xCtot
            elif xCtot > xC:
                # Throw an error if input C abundance is smaller than
                # what is accounted for in initial conditions
                raise despoticError(
                    "input C abundance is " + 
                    str(xC) + ", but total input C, C+, CHx, CO, " + 
                    "HCO+ abundance is " + str(xCtot))

            # O
            if 'o' in emList:
                abd['O'] = emList['o'].abundance
            elif info is None:
                abd['O'] = _xOdefault - abd['OHx'] - abd['CO'] - \
                    abd['HCO+']
            elif 'xO' in info:
                abd['O'] = info['xO'] - abd['OHx'] - abd['CO'] - \
                    abd['HCO+']
            else:
                abd['O'] = _xOdefault - abd['OHx'] - abd['CO'] - \
                    abd['HCO+']

            # As with C, make sure all O is accounted for, and if not
            # park the extra in OI
            if info is None:
                xO = _xOdefault
            elif 'xC' in info:
                xO = info['xO']
            else:
                xO = _xOdefault
            xOtot = abd['OHx'] + abd['CO'] + abd['HCO+'] + abd['O']
            if xOtot < xO:
                # Print warning if we're altering existing O
                # abundance.
                if 'o' in emList:
                    print("Warning: input O abundance is " + 
                          str(xO) + ", but total input O, OHx, CO, " + 
                          "HCO+ abundance is " + str(xOtot) + 
                          "; increasing xO to " + str(abd['O']+xO-xOtot))
                abd['O'] += xO - xOtot
            elif xOtot > xO:
                # Throw an error if input O abundance is smaller than
                # what is accounted for in initial conditions
                raise despoticError(
                    "input O abundance is " + 
                    str(xO) + ", but total input O, OHx, CO, " + 
                    "HCO+ abundance is " + str(xOtot))

            # Finally, make sure all H nuclei are accounted for and
            # that all hydrogenic abundances are >= 0
            abd1 = self.abundances
            xH = abd1['H+'] + abd1['OHx'] + abd1['CHx'] + abd1['HCO+'] + \
                 abd1['H'] + 2*abd1['H2'] + 3*abd1['H3+']
            if (abs(xH-1.0) > 1.0e-8):
                raise despoticError(
                    "input hydrogen abundances " +
                    "add up to xH = "+str(xH)+" != 1!")
            if (abd1['H+'] < 0) or \
               (abd1['OHx'] < 0) or (abd1['CHx'] < 0) or \
               (abd1['HCO+'] < 0) or (abd1['H'] < 0) or \
               (abd1['H2'] < 0) or (abd1['H3+'] < 0):
                raise despoticError(
                    "abundances of some " + 
                    "hydrogenic species are < 0; abundances " + 
                    "are: " + repr(abd1))

        # Get rate coefficients in starting state; we will use these
        # to put some species close to equilibrium initially
        abd1 = self.abundances
        cfac = self.cfac
        n = self.nH * cfac
        rcoef = _twobody_ratecoef(
            self.temp, self.cloud.Td, n, abd1['H2']*n, abd1['e-']*n, 
            np.exp(-self.AV)*self.chi)

        # Set initial He+ to equilibrium value between creation by
        # cosmic rays and destruction by recombination with free
        # electrons, H2, and CO
        abd['He+'] \
            = self.xHe * self.ionRate / \
            (n * (abd1['H2'] * (rcoef[3]+rcoef[18]) +
                  abd1['CO'] * rcoef[4] +
                  abd1['e-'] * rcoef[9]))

        # Set initial H3+ in the same way as for He+; then correct H2
        # abundance to conserve total hydrogen
        abd['H3+'] \
            = abd1['H2'] * self.ionRate / \
            (n * (abd1['C'] * rcoef[0] +
                  abd1['O'] * rcoef[1] +
                  abd1['CO'] * rcoef[2] +
                  abd1['e-'] * (rcoef[10] + rcoef[11])))
        abd['H2'] -= 1.5*abd['H3+']

        # Initial M+
        abd['M+'] = self.xM