Source code for astromugs.pipeline.Grid

from __future__ import annotations

import numpy as np

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from astromugs.utils.struct import DiskParams #only need this for annotations
    from astromugs.utils.thermal import WaveParams #only need this for annotations

    

[docs] class Grid: """Spatial and wavelength grid for young stellar object modeling. Holds the coordinate system, physical quantities (density, temperature, radiation field), and optional chemistry sub-grids used by radiative transfer and chemical codes. Attributes ---------- params : DiskParams Disk/envelope structural parameters (radii, resolution, etc.). wave : WaveParams Wavelength grid parameters. density : list of ndarray Total (gas + dust) density components, in g/cm^3. dustdensity : list of ndarray Dust density components for radiative transfer, in g/cm^3. gasdensity_chem : list of ndarray Gas density components for chemistry, in g/cm^3. dustdensity_chem : list of ndarray Dust density components for chemistry, in g/cm^3. dustdensity_single_chem : list of ndarray Single-grain-population dust density for chemistry, in g/cm^3. hg_chem : list of ndarray Gas scale height arrays for the chemistry grid, in AU. tgas_chem : list of ndarray Gas temperature arrays for the chemistry grid, in K. temperature : list of ndarray Dust temperature components, in K. localfield : list of ndarray Local radiation field components. avz : list of ndarray Vertical visual extinction components, in mag. stars : list Stellar source objects. isrf : list Interstellar radiation field components. dust : list Dust opacity/property objects. accretionheating : list of ndarray Viscous accretion heating rate components. chemradii : list of ndarray Radial coordinate arrays imported from existing chemistry models, in AU. chemparam : list Parameter sets imported from existing chemistry models. chemmodel : dict Chemical abundance grids keyed by species name. """
[docs] def __init__(self, params: DiskParams, wave: WaveParams ): """Initialize the Grid with disk parameters and wavelength settings. Parameters ---------- params : DiskParams Disk/envelope structural parameters. wave : WaveParams Wavelength grid parameters. """ self.params = params self.wave = wave self.density = [] self.dustdensity = [] self.gasdensity_chem = [] self.dustdensity_chem = [] self.dustdensity_single_chem = [] self.hg_chem = [] self.tgas_chem = [] self.temperature = [] self.localfield = [] self.avz = [] self.stars = [] self.isrf = [] self.dust = [] self.accretionheating = [] self.chemradii = [] self.chemparam = [] self.chemmodel = {}
#----- # ADD/CREATE GRID STRUCTURES THAT EXIST OR THAT HAVE BEEN CREATED. #-----
[docs] def add_star(self, star): """Append a stellar source to the grid. Parameters ---------- star : object Stellar source object containing luminosity, temperature, etc. """ self.stars.append(star)
[docs] def add_isrf(self, isrf): """Append an interstellar radiation field component. Parameters ---------- isrf : object Interstellar radiation field specification. """ self.isrf.append(isrf)
[docs] def add_temperature(self, temperature): """Append a dust temperature component. Parameters ---------- temperature : ndarray Dust temperature array, in K. """ self.temperature.append(temperature)
[docs] def add_localfield(self, localfield): """Append a local radiation field component. Parameters ---------- localfield : ndarray Local radiation field array. """ self.localfield.append(localfield)
[docs] def add_density(self, density): """Append a total density component. Parameters ---------- density : ndarray Total (gas + dust) density array, in g/cm^3. """ self.density.append(density)
[docs] def add_dustdensity(self, density): """Append a dust density component for radiative transfer. Parameters ---------- density : ndarray Dust density array, in g/cm^3. """ self.dustdensity.append(density)
[docs] def add_dustdensity_chem(self, density): """Append a dust density component for chemistry. Parameters ---------- density : ndarray Dust density array for the chemistry grid, in g/cm^3. """ self.dustdensity_chem.append(density)
[docs] def add_dustdensity_single_chem(self, density): """Append a single-grain-population dust density for chemistry. Parameters ---------- density : ndarray Single-grain dust density array for the chemistry grid, in g/cm^3. """ self.dustdensity_single_chem.append(density)
[docs] def add_gasdensity_chem(self, density): """Append a gas density component for chemistry. Parameters ---------- density : ndarray Gas density array for the chemistry grid, in g/cm^3. """ self.gasdensity_chem.append(density)
[docs] def add_gastemperature_chem(self, gas_temperature): """Append a gas temperature component for chemistry. Parameters ---------- gas_temperature : ndarray Gas temperature array for the chemistry grid, in K. """ self.tgas_chem.append(gas_temperature)
[docs] def add_hg_chem(self, hg): """Append a gas scale height array for chemistry. Parameters ---------- hg : ndarray Gas scale height array, in AU. """ self.hg_chem.append(hg)
[docs] def add_avz(self, av_z): """Append a vertical visual extinction component. Parameters ---------- av_z : ndarray Vertical visual extinction array, in mag. """ self.avz.append(av_z)
[docs] def add_dust(self, dust): """Append a dust opacity/property object. Parameters ---------- dust : object Dust opacity or property specification. """ self.dust.append(dust)
[docs] def add_accretionheating(self, q_visc): """Append a viscous accretion heating rate component. Parameters ---------- q_visc : ndarray Viscous heating rate array. """ self.accretionheating.append(q_visc)
[docs] def add_existingchemradii(self,existingchemradii): """Append radial coordinates from an existing chemistry model. Parameters ---------- existingchemradii : ndarray Array of radial and vertical grid points from an existing chemistry model, in AU. """ self.chemradii.append(existingchemradii)
[docs] def add_existingchemparam(self,existingchemparam): """Append parameters from an existing chemistry model. Parameters ---------- existingchemparam : object Parameter set from an existing chemistry model. """ self.chemparam.append(existingchemparam)
[docs] def add_existingchemmodel(self,existingchemmodel, species): """Store abundance data from an existing chemistry model for a species. Parameters ---------- existingchemmodel : ndarray Abundance grid for the given species. species : str Chemical species name used as the dictionary key. """ self.chemmodel[species] = existingchemmodel
#----- # SET GRID STRUCTURES. #-----
[docs] def set_cartesian_grid(self, xmin, xmax, nx): """Build a uniform Cartesian grid and return edges and cell centres. The same range is used for all three axes (x, y, z). Parameters ---------- xmin : float Minimum coordinate value, in AU. xmax : float Maximum coordinate value, in AU. nx : int Number of grid edges along each axis. Returns ------- edges : ndarray, shape (3, nx) Cell edge coordinates stacked as (x, y, z). centres : ndarray, shape (3, nx-1) Cell centre coordinates stacked as (w1, w2, w3). """ self.coordsystem = "cartesian" x = np.linspace(xmin, xmax, nx) y = np.linspace(xmin, xmax, nx) z = np.linspace(xmin, xmax, nx) w1 = 0.5*(x[0:x.size-1] + x[1:x.size]) w2 = 0.5*(y[0:y.size-1] + y[1:y.size]) w3 = 0.5*(z[0:z.size-1] + z[1:z.size]) return np.stack((x, y, z)), np.stack((w1, w2, w3))
[docs] def set_spherical_grid(self, r_edge=None, theta_edge=None, phi_edge=None, log=True): """Set the spherical grid. If edge arrays are provided (e.g. read from an existing amr_grid.inp), use them directly. Otherwise, compute edges from the DiskParams (rin, rout, nr, ntheta, nphi). Parameters ---------- r_edge : array-like, optional Radial cell edges in au. If None, computed from params. theta_edge : array-like, optional Polar cell edges in radians. If None, computed from params. phi_edge : array-like, optional Azimuthal cell edges in radians. If None, computed from params. log : bool Use logarithmic spacing for radial edges (only when computing from params). """ self.coordsystem = "spherical" if r_edge is not None: r_edge = np.asarray(r_edge) theta_edge = np.asarray(theta_edge) phi_edge = np.asarray(phi_edge) else: rmin = self.params.rin rmax = self.params.rout nr = self.params.nr ntheta = self.params.ntheta nphi = self.params.nphi if log: r_edge = np.logspace(np.log10(rmin), np.log10(rmax), nr, base=10) else: r_edge = np.linspace(rmin, rmax, nr) theta_edge = np.linspace(0.0, np.pi, ntheta) phi_edge = np.linspace(0.0, 2*np.pi, nphi) self.r = 0.5*(r_edge[0:r_edge.size-1] + r_edge[1:r_edge.size]) self.theta = 0.5*(theta_edge[0:theta_edge.size-1] + theta_edge[1:theta_edge.size]) self.phi = 0.5*(phi_edge[0:phi_edge.size-1] + phi_edge[1:phi_edge.size]) self.nr = r_edge.size self.ntheta = theta_edge.size self.nphi = phi_edge.size self.r_edge = r_edge self.theta_edge = theta_edge self.phi_edge = phi_edge
[docs] def set_chemdisk_grid(self, r, max_H=4, nz_chem=64, stretch=1.0): """Build a vertical chemistry grid for a disk model. The vertical coordinate runs from ``max_H`` scale heights down to the midplane. Spacing is uniform by default; setting ``stretch`` redistributes the same number of points so that resolution is concentrated near the midplane or near the disk surface. Parameters ---------- r : array_like Radial positions, in AU. max_H : float, optional Upper limit of the grid expressed in gas scale heights. nz_chem : int, optional Number of vertical grid points (same at all radii). stretch : float, optional Power-law exponent controlling the vertical spacing. ``stretch=1.0`` (default) gives uniform spacing. ``stretch>1.0`` concentrates points near the midplane (z=0), useful when high chemical resolution is needed inside the disk. ``stretch<1.0`` concentrates points near the disk surface (z=max_H), useful when the atmosphere needs finer sampling. """ t = np.linspace(0, 1, nz_chem) z = (1. - t**stretch) * max_H self.rchem = np.array(r) self.zchem = z self.nz_chem = nz_chem
[docs] def set_chem_grid(self, r, z0=0, zmax=None, msize=None, nbcells=70, stretch=1.0): """Build a custom spatial grid for chemistry (disk, envelope, etc.). Two modes are available. If *zmax* is given, the vertical extent is uniform at all radii. If *msize* is given, the maximum altitude at each radius follows a spherical envelope boundary. Vertical coordinates are stored in decreasing order as required by Nautilus. Parameters ---------- r : ndarray 1-D array of radial positions, in AU. Must lie within the RADMC-3D model domain. z0 : float, optional Minimum altitude, in AU. zmax : float, optional Maximum altitude applied uniformly at all radii, in AU. msize : float, optional Model size (sphere radius) in AU. When provided, the maximum altitude at each radius is computed as sqrt(msize^2 - r^2). nbcells : int, optional Number of vertical cells (same for all radii). stretch : float, optional Power-law exponent controlling the vertical spacing. ``stretch=1.0`` (default) gives uniform spacing. ``stretch>1.0`` concentrates points near the midplane (z=z0), useful when high chemical resolution is needed inside the disk. ``stretch<1.0`` concentrates points near the surface (z=zmax), useful when the atmosphere needs finer sampling. """ self.nz_chem = nbcells t = np.linspace(0, 1, nbcells) if zmax != None: #if user provides maximum altitude in au, it sets the maximum z at all radii. By default the minimum value z0 is 0. That creates a 2D structure. self.rchem = np.array(r) z = np.zeros((len(self.rchem), nbcells)) for idx, rval in enumerate(self.rchem): z[idx,:] = z0 + (zmax - z0) * t**stretch self.zchem = np.flip(np.array(z), axis=1) #flip because the user gives increasing values and nautilus needs decreasing values. if msize != None: #if user wants the altitude max such that the model is a sphere i.e. zmax at each radius follows the spherical structure. zmax = np.sqrt(msize**2 - r**2) # gives max altitude at each radius (polar coordinates). zmax = zmax[:-1] # remove the last value because it is zmax = 0 au. self.rchem = r[:-1] # because we removed the last zmax. z = np.zeros((len(self.rchem), nbcells)) for idx, zmax_x in enumerate(zmax): z[idx,:] = zmax_x * t**stretch self.zchem = np.flip(z, axis=1) #flip because the user gives increasing values and nautilus needs decreasing values.
#dz = np.tan(np.pi/nbthetas)*self.rchem #0.0175 is pi/number of thetas.
[docs] def set_wavelength_grid(self, log=True): """Build the wavelength grid used by the radiative transfer. Parameters ---------- log : bool, optional If True, use logarithmic spacing; otherwise linear spacing. """ lmin = self.wave.lmin lmax = self.wave.lmax nlam = self.wave.nlam if log: self.lam = np.logspace(np.log10(lmin), np.log10(lmax), \ nlam) else: self.lam = np.linspace(lmin, lmax, nlam)
[docs] def set_mcmonowavelength_grid(self, log=True): """Build the monochromatic Monte Carlo wavelength grid. Parameters ---------- log : bool, optional If True, use logarithmic spacing; otherwise linear spacing. """ lmin_mono = self.wave.lmin_mono lmax_mono = self.wave.lmax_mono nlam_mono = self.wave.nlam_mono if log: self.monolam = np.logspace(np.log10(lmin_mono), np.log10(lmax_mono), \ nlam_mono) else: self.monolam = np.linspace(lmin_mono, lmax_mono, nlam_mono)