Source code for pyratbay.opacity.clouds.gray

# Copyright (c) 2021-2026 Cubillos & Blecic
# Pyrat Bay is open-source software under the GPL-2.0 license (see LICENSE)

__all__ = [
    'CCSgray',
    'Deck',
]

import numpy as np
import scipy.interpolate as si

from ... import constants as pc
from ... import tools as pt


[docs] class CCSgray(): """ Constant cross-section gray cloud model. """ def __init__(self, pressure, wn): self.name = 'ccsgray' # Model name is lowercased class name self.pressure = pressure self.wn = wn self.nwave = len(wn) self.nlayers = len(pressure) # log10 of cross-section scale factor, top, and bottom pressure (bar) self.pars = [0.0, -4.0, 2.0] # Fitting-parameter names: self.pnames = [ 'log_k_gray', 'log_p_top', 'log_p_bot', ] self.texnames = [ r'$\log_{10}(f_{\rm gray})$', r'$\log_{10}(p_{\rm top})\ ({\rm bar})$', r'$\log_{10}(p_{\rm bot})\ ({\rm bar})$', ] self.s0 = 5.31e-27 # Default coss-section (cm-2 molec-1) self.npars = len(self.pars) # Number of model fitting parameters self.ec = np.zeros((self.nlayers, self.nwave))
[docs] def calc_cross_section(self): """ Calculate a uniform gray-cloud cross section in cm2 molec-1: cross section = s0 * 10**pars[0], between layers with pressure 10**pars[1] -- 10**pars[2] bar (top and bottom layers, respectively). s0 is the H2 Rayleigh cross section at 0.35 um. Parameters ---------- wn: 1D float ndarray Wavenumber array in cm-1. """ # Get indices for cloud layer boundaries: p_top = 10**self.pars[2] p_bottom = 10**self.pars[1] p_mask = (self.pressure >= p_bottom) & (self.pressure <= p_top) # Gray opacity cross section in cm2 molec-1 cs = np.zeros(self.nlayers) cs[p_mask] = 10**self.pars[0] * self.s0 return cs
def calc_extinction_coefficient(self, temperature, pars=None, layer=None): # Cross section (in cm2 molecule-1) if pars is not None: self.pars[:] = pars cs = self.calc_cross_section() # Densities in molecules cm-3: density = self.pressure*pc.bar / temperature / pc.k if layer is not None: return cs[layer] * density[layer] # Extinction coefficient (cm-1): self.ec = np.expand_dims(cs*density, axis=1) * np.ones(self.nwave) return self.ec def __str__(self): fw = pt.Formatted_Write() fw.write(f"Model name (name): '{self.name}'") fw.write(f'Number of model parameters (npars): {self.npars}') fw.write('Parameter name Value\n' ' (pnames) (pars)\n') for pname, param in zip(self.pnames, self.pars): fw.write(' {:15s} {:10.3e}', pname, param) fw.write('Extinction coefficient (ec, cm-1):\n{}', self.ec, fmt={'float':'{:.3e}'.format}, edge=3) return fw.text
[docs] class Deck(): """ Instantly opaque gray cloud deck at given pressure. """ def __init__(self, pressure, wn): self.name = 'deck' self.pressure = pressure self.wn = wn self.nwave = len(wn) self.nlayers = len(pressure) self.pars = [-1.0] # log10(Pressure[bar]) of cloud top self.npars = len(self.pars) # Number of model fitting parameters self.ec = np.zeros((self.nlayers, self.nwave)) # Fitting-parameter names (plain text and figure labels): self.pnames = ['log_p_cl'] self.texnames = [r'$\log\ p_{\rm cl}$'] self.itop = None self.rsurf = 0.0 self.tsurf = 0.0
[docs] def calc_extinction_coefficient( self, radius, temperature, pars=None, layer=None, ): """ Calculate gray-cloud deck that's transparent above ptop, and becomes instantly opaque at ptop, with ptop (bar) = 10**pars[0]. Parameters ---------- radius: 1D float ndarray Atmospheric radius profile (in cm). temperature: 1D float ndarray Atmospheric temperature profile (in Kelvin degree). pars: 1D float iterable If not None, update the model parameters with input values layer: integer If not None, check whether the cloud top is above or below the given atmospheric layer at this index. """ if pars is not None: self.pars[:] = pars ptop = 10**self.pars[0] # Index of layer directly below cloud top: if ptop >= self.pressure[-1]: # Atmosphere boundary cases self.itop = self.nlayers-1 elif ptop < self.pressure[0]: self.itop = 1 else: self.itop = np.where(self.pressure>=ptop)[0][0] if layer is not None: # Just a boolean indicating whether the cloud top is above layer: return np.zeros(self.nwave) + int(layer > self.itop) # Radius and temperature at the cloud top: self.tsurf = float(si.interp1d(self.pressure, temperature)(ptop)) self.rsurf = float(si.interp1d(self.pressure, radius)(ptop)) return self.ec
def __str__(self): fw = pt.Formatted_Write() fw.write("Model name (name): '{}'", self.name) fw.write('Number of model parameters (npars): {}', self.npars) fw.write('Parameter name Value\n' ' (pnames) (pars)\n') for pname, param in zip(self.pnames, self.pars): fw.write(' {:15s} {: .3e}', pname, param) fw.write('Index of atmospheric layer at or directly below cloud ' f'top: {self.itop}') fw.write('Cloud-top pressure: {:.4e} bar', 10**self.pars[0]) fw.write('Cloud-top altitude: {:.2f} km', self.rsurf/pc.km) fw.write('Cloud-top temperature: {:.2f} K', self.tsurf) return fw.text