Skip to content

GLOW 2D Generation in GEO Coordinates

glow2d.geo_model

Run GLOW model looking along heading from the current location and return the model output in (T, R) geocentric coordinates where T is angle in radians from the current location along the great circle following current heading, and R is altitude in kilometers. R is in an uniform grid with n_alt points.

Parameters:

Name Type Description Default
time datetime

Datetime of GLOW calculation.

required
lat Numeric

Latitude of starting location.

required
lon Numeric

Longitude of starting location.

required
heading Numeric

Heading (look direction).

required
max_alt Numeric

Maximum altitude where intersection is considered (km). Defaults to 1000, i.e. exobase.

1000
n_pts int

Number of GEO coordinate angular grid points (i.e. number of GLOW runs), must be even and > 20. Defaults to 50.

50
n_bins int

Number of energy bins. Defaults to 100.

100
n_alt int

Number of altitude bins, must be > 100. Defaults to None, i.e. uses same number of bins as a single GLOW run.

None
n_threads int

Number of threads for parallel GLOW runs. Set to None to use all system threads. Defaults to None.

None
show_progress bool

Use TQDM to show progress of GLOW model calculations. Defaults to True.

True
kwargs dict

Passed to glowpython.generic.

{}

Returns:

Name Type Description
bds xarray.Dataset

Ionospheric parameters and brightnesses (with production and loss) in GEO coordinates.

Raises:

Type Description
ValueError

Number of position bins can not be odd.

ValueError

Number of position bins can not be < 20.

ValueError

Resampling can not be < 0.5.

Warns

ResourceWarning: Number of threads requested is more than available system threads.

Source code in glow2d/_glow2d.py
def geo_model(time: datetime, lat: Numeric, lon: Numeric, heading: Numeric, max_alt: Numeric = 1000, n_pts: int = 50, n_bins: int = 100, *, n_alt: int = None, n_threads: int = None, show_progress: bool = True, **kwargs) -> xarray.Dataset:
    """Run GLOW model looking along heading from the current location and return the model output in
    (T, R) geocentric coordinates where T is angle in radians from the current location along the great circle
    following current heading, and R is altitude in kilometers. R is in an uniform grid with `n_alt` points.

    Args:
        time (datetime): Datetime of GLOW calculation.
        lat (Numeric): Latitude of starting location.
        lon (Numeric): Longitude of starting location.
        heading (Numeric): Heading (look direction).
        max_alt (Numeric, optional): Maximum altitude where intersection is considered (km). Defaults to 1000, i.e. exobase.
        n_pts (int, optional): Number of GEO coordinate angular grid points (i.e. number of GLOW runs), must be even and > 20. Defaults to 50.
        n_bins (int, optional): Number of energy bins. Defaults to 100.
        n_alt (int, optional): Number of altitude bins, must be > 100. Defaults to `None`, i.e. uses same number of bins as a single GLOW run.
        n_threads (int, optional): Number of threads for parallel GLOW runs. Set to `None` to use all system threads. Defaults to `None`.
        show_progress (bool, optional): Use TQDM to show progress of GLOW model calculations. Defaults to `True`.
        kwargs (dict, optional): Passed to `glowpython.generic`.

    Returns:
        bds (xarray.Dataset): Ionospheric parameters and brightnesses (with production and loss) in GEO coordinates.

    Raises:
        ValueError: Number of position bins can not be odd.
        ValueError: Number of position bins can not be < 20.
        ValueError: Resampling can not be < 0.5.

    Warns:
        ResourceWarning: Number of threads requested is more than available system threads.
    """
    grobj = glow2d_geo(time, lat, lon, heading, max_alt,
                       n_pts, n_bins, n_alt=n_alt, uniformize_glow=True, n_threads=n_threads, show_progress=show_progress, **kwargs)
    bds = grobj.run_model()
    return bds

Compute GLOW model on the great circle passing through the origin location along the specified bearing. The result is presented in a geocentric coordinate system.

Source code in glow2d/_glow2d.py
class glow2d_geo:
    """Compute GLOW model on the great circle passing through the origin location along the specified bearing. 
    The result is presented in a geocentric coordinate system.
    """

    def __init__(self, time: datetime, lat: Numeric, lon: Numeric, heading: Numeric, max_alt: Numeric = 1000, n_pts: int = 50, n_bins: int = 100, *, n_alt: int = None, uniformize_glow: bool = True, n_threads: int = None, full_circ: bool = False, show_progress: bool = True, **kwargs):
        """Create a GLOWRaycast object.

        Args:
            time (datetime): Datetime of GLOW calculation.
            lat (Numeric): Latitude of starting location.
            lon (Numeric): Longitude of starting location.
            heading (Numeric): Heading (look direction).
            max_alt (Numeric, optional): Maximum altitude where intersection is considered (km). Defaults to 1000, i.e. exobase.
            n_pts (int, optional): Number of GEO coordinate angular grid points (i.e. number of GLOW runs), must be even and > 20. Defaults to 50.
            n_bins (int, optional): Number of energy bins. Defaults to 100.
            n_alt (int, optional): Number of altitude bins, must be > 100. Used only when `uniformize_glow` is set to `True` (default). Defaults to `None`, i.e. uses same number of bins as a single GLOW run.
            uniformize_glow (bool, optional): Interpolate GLOW output to an uniform altitude grid. `n_alt` is ignored if this option is set to `False`. Defaults to `True`.
            n_threads (int, optional): Number of threads for parallel GLOW runs. Set to `None` to use all system threads. Defaults to `None`.
            full_circ (bool, optional): For testing only, do not use. Defaults to False.
            show_progress (bool, optional): Use TQDM to show progress of GLOW model calculations. Defaults to True.
            kwargs (dict, optional): Passed to `glowpython.generic`.

        Raises:
            ValueError: Number of position bins can not be odd.
            ValueError: Number of position bins can not be < 20.
            ValueError: Number of altitude bins can not be < 100.

        Warns:
            ResourceWarning: Number of threads requested is more than available system threads.
        """
        if n_pts % 2:
            raise ValueError('Number of position bins can not be odd.')
        if n_pts < 20:
            raise ValueError('Number of position bins can not be < 20.')
        if n_threads is None:
            n_threads = N_CPUS
        if n_threads > N_CPUS:
            warnings.warn('Number of requested threads (%d > %d)' % (n_threads, N_CPUS), ResourceWarning)
        self._uniform_glow = uniformize_glow
        self._pt = Point(lat, lon)  # instrument loc
        self._time = time  # time of calc
        if n_alt is not None and n_alt < 100:
            raise ValueError('Number of altitude bins can not be < 100')
        if n_alt is None:
            n_alt = 100  # default
        max_d = 6400 * np.pi if full_circ else EARTH_RADIUS * \
            np.arccos(EARTH_RADIUS / (EARTH_RADIUS + max_alt)
                      )  # find maximum distance where LOS intersects exobase # 6400 * np.pi
        # points on the earth where we need to sample
        self._show_prog = show_progress
        self._kwargs = kwargs
        distpts = np.linspace(0, max_d, n_pts, endpoint=True)
        self._locs = []
        self._nbins = n_bins  # number of energy bins (for later)
        self._nthr = n_threads  # number of threads (for later)
        for d in distpts:  # for each distance point
            dest = GreatCircleDistance(
                kilometers=d).destination(self._pt, heading)  # calculate lat, lon of location at that distance along the great circle
            self._locs.append((dest.latitude, dest.longitude))  # store it
        if full_circ:  # for fun, _get_angle() does not wrap around for > 180 deg
            npt = self._locs[-1]
            for d in distpts:
                dest = GreatCircleDistance(
                    kilometers=d).destination(npt, heading)
                self._locs.append((dest.latitude, dest.longitude))
        self._angs = self._get_angle()  # get the corresponding angles
        if full_circ:  # fill the rest if full circle
            self._angs[len(self._angs) // 2:] = 2*np.pi - \
                self._angs[len(self._angs) // 2:]
        self._bds = None

    def _get_angle(self) -> np.ndarray:  # get haversine angles between two lat, lon coords
        angs = []
        for pt in self._locs:
            ang = haversine(self._locs[0], pt, unit=Unit.RADIANS)
            angs.append(ang)
        return np.asarray(angs)

    @staticmethod
    def _uniformize_glow(iono: xarray.Dataset) -> xarray.Dataset:
        alt_km = iono.alt_km.values
        alt = np.linspace(alt_km.min(), alt_km.max(), len(alt_km))  # change to custom
        unit_keys = ["Tn", "O", "N2", "O2", "NO", "NeIn", "NeOut", "ionrate",
                     "O+", "O2+", "NO+", "N2D", "pedersen", "hall", "Te", "Ti"]
        state_keys = ['production', 'loss', 'excitedDensity']
        data_vars = {}
        for key in unit_keys:
            out = np.interp(alt, alt_km, iono[key].values)
            data_vars[key] = (('alt_km'), out)
        bds = xarray.Dataset(data_vars=data_vars, coords={'alt_km': alt})
        ver = np.zeros(iono['ver'].shape, dtype=float)
        for idx in range(len(iono.wavelength)):
            ver[:, idx] += np.interp(alt, alt_km, iono['ver'].values[:, idx])
        ver = xr.DataArray(
            ver,
            coords={'alt_km': alt, 'wavelength': iono.wavelength.values},
            dims=('alt_km', 'wavelength'),
            name='ver'
        )
        data_vars = {}
        for key in state_keys:
            out = np.zeros(iono[key].shape)
            inp = iono[key].values
            for idx in range(len(iono.state)):
                out[:, idx] += np.interp(alt, alt_km, inp[:, idx])
            data_vars[key] = (('alt_km', 'state'), out)
        prodloss = xarray.Dataset(data_vars=data_vars,
                                  coords={'alt_km': alt, 'state': iono.state.values})
        precip = iono['precip']
        bds: xarray.Dataset = xr.merge((bds, ver, prodloss, precip))
        _ = list(map(lambda x: bds[x].attrs.update(iono[x].attrs), tuple(bds.data_vars.keys())))
        bds.attrs.update(iono.attrs)
        return bds

    # calculate glow model for one location
    def _calc_glow_single_noprecip(self, index):
        d = self._locs[index]
        iono = glow.generic(self._time, d[0], d[1], self._nbins, **self._kwargs)
        if self._uniform_glow:
            iono = self._uniformize_glow(iono)
        return iono

    def _calc_glow_noprecip(self) -> xarray.Dataset:  # run glow model calculation
        if self._show_prog:
            self._dss = MAP_FCN(self._calc_glow_single_noprecip, range(
                len(self._locs)), max_workers=self._nthr)
        else:
            ppool = Pool(self._nthr)
            self._dss = ppool.map(self._calc_glow_single_noprecip, range(
                len(self._locs)))

        # for dest in tqdm(self._locs):
        #     self._dss.append(glow.no_precipitation(time, dest[0], dest[1], self._nbins))
        bds: xarray.Dataset = xr.concat(
            self._dss, pd.Index(self._angs, name='angle'))
        latlon = np.asarray(self._locs)
        bds = bds.assign_coords(lat=('angle', latlon[:, 0]))
        bds = bds.assign_coords(lon=('angle', latlon[:, 1]))
        return bds

    def run_model(self) -> xarray.Dataset:
        """Run the GLOW model calculation to get the model output in GEO coordinates.

        Returns:
            xarray.Dataset: GLOW model output in GEO coordinates.
        """
        if self._bds is not None:
            return self._bds
        # calculate the GLOW model for each lat-lon point determined in init()
        bds = self._calc_glow_noprecip()
        unit_desc_dict = {
            'angle': ('radians', 'Angle of location w.r.t. radius vector at origin (starting location)'),
            'lat': ('degree', 'Latitude of locations'),
            'lon': ('degree', 'Longitude of location')
        }
        _ = list(map(lambda x: bds[x].attrs.update(
            {'units': unit_desc_dict[x][0], 'description': unit_desc_dict[x][1]}), unit_desc_dict.keys()))
        self._bds = bds
        return self._bds  # return the calculated

__init__(time, lat, lon, heading, max_alt=1000, n_pts=50, n_bins=100, *, n_alt=None, uniformize_glow=True, n_threads=None, full_circ=False, show_progress=True, **kwargs)

Create a GLOWRaycast object.

Parameters:

Name Type Description Default
time datetime

Datetime of GLOW calculation.

required
lat Numeric

Latitude of starting location.

required
lon Numeric

Longitude of starting location.

required
heading Numeric

Heading (look direction).

required
max_alt Numeric

Maximum altitude where intersection is considered (km). Defaults to 1000, i.e. exobase.

1000
n_pts int

Number of GEO coordinate angular grid points (i.e. number of GLOW runs), must be even and > 20. Defaults to 50.

50
n_bins int

Number of energy bins. Defaults to 100.

100
n_alt int

Number of altitude bins, must be > 100. Used only when uniformize_glow is set to True (default). Defaults to None, i.e. uses same number of bins as a single GLOW run.

None
uniformize_glow bool

Interpolate GLOW output to an uniform altitude grid. n_alt is ignored if this option is set to False. Defaults to True.

True
n_threads int

Number of threads for parallel GLOW runs. Set to None to use all system threads. Defaults to None.

None
full_circ bool

For testing only, do not use. Defaults to False.

False
show_progress bool

Use TQDM to show progress of GLOW model calculations. Defaults to True.

True
kwargs dict

Passed to glowpython.generic.

{}

Raises:

Type Description
ValueError

Number of position bins can not be odd.

ValueError

Number of position bins can not be < 20.

ValueError

Number of altitude bins can not be < 100.

Warns

ResourceWarning: Number of threads requested is more than available system threads.

Source code in glow2d/_glow2d.py
def __init__(self, time: datetime, lat: Numeric, lon: Numeric, heading: Numeric, max_alt: Numeric = 1000, n_pts: int = 50, n_bins: int = 100, *, n_alt: int = None, uniformize_glow: bool = True, n_threads: int = None, full_circ: bool = False, show_progress: bool = True, **kwargs):
    """Create a GLOWRaycast object.

    Args:
        time (datetime): Datetime of GLOW calculation.
        lat (Numeric): Latitude of starting location.
        lon (Numeric): Longitude of starting location.
        heading (Numeric): Heading (look direction).
        max_alt (Numeric, optional): Maximum altitude where intersection is considered (km). Defaults to 1000, i.e. exobase.
        n_pts (int, optional): Number of GEO coordinate angular grid points (i.e. number of GLOW runs), must be even and > 20. Defaults to 50.
        n_bins (int, optional): Number of energy bins. Defaults to 100.
        n_alt (int, optional): Number of altitude bins, must be > 100. Used only when `uniformize_glow` is set to `True` (default). Defaults to `None`, i.e. uses same number of bins as a single GLOW run.
        uniformize_glow (bool, optional): Interpolate GLOW output to an uniform altitude grid. `n_alt` is ignored if this option is set to `False`. Defaults to `True`.
        n_threads (int, optional): Number of threads for parallel GLOW runs. Set to `None` to use all system threads. Defaults to `None`.
        full_circ (bool, optional): For testing only, do not use. Defaults to False.
        show_progress (bool, optional): Use TQDM to show progress of GLOW model calculations. Defaults to True.
        kwargs (dict, optional): Passed to `glowpython.generic`.

    Raises:
        ValueError: Number of position bins can not be odd.
        ValueError: Number of position bins can not be < 20.
        ValueError: Number of altitude bins can not be < 100.

    Warns:
        ResourceWarning: Number of threads requested is more than available system threads.
    """
    if n_pts % 2:
        raise ValueError('Number of position bins can not be odd.')
    if n_pts < 20:
        raise ValueError('Number of position bins can not be < 20.')
    if n_threads is None:
        n_threads = N_CPUS
    if n_threads > N_CPUS:
        warnings.warn('Number of requested threads (%d > %d)' % (n_threads, N_CPUS), ResourceWarning)
    self._uniform_glow = uniformize_glow
    self._pt = Point(lat, lon)  # instrument loc
    self._time = time  # time of calc
    if n_alt is not None and n_alt < 100:
        raise ValueError('Number of altitude bins can not be < 100')
    if n_alt is None:
        n_alt = 100  # default
    max_d = 6400 * np.pi if full_circ else EARTH_RADIUS * \
        np.arccos(EARTH_RADIUS / (EARTH_RADIUS + max_alt)
                  )  # find maximum distance where LOS intersects exobase # 6400 * np.pi
    # points on the earth where we need to sample
    self._show_prog = show_progress
    self._kwargs = kwargs
    distpts = np.linspace(0, max_d, n_pts, endpoint=True)
    self._locs = []
    self._nbins = n_bins  # number of energy bins (for later)
    self._nthr = n_threads  # number of threads (for later)
    for d in distpts:  # for each distance point
        dest = GreatCircleDistance(
            kilometers=d).destination(self._pt, heading)  # calculate lat, lon of location at that distance along the great circle
        self._locs.append((dest.latitude, dest.longitude))  # store it
    if full_circ:  # for fun, _get_angle() does not wrap around for > 180 deg
        npt = self._locs[-1]
        for d in distpts:
            dest = GreatCircleDistance(
                kilometers=d).destination(npt, heading)
            self._locs.append((dest.latitude, dest.longitude))
    self._angs = self._get_angle()  # get the corresponding angles
    if full_circ:  # fill the rest if full circle
        self._angs[len(self._angs) // 2:] = 2*np.pi - \
            self._angs[len(self._angs) // 2:]
    self._bds = None

run_model()

Run the GLOW model calculation to get the model output in GEO coordinates.

Returns:

Type Description
xarray.Dataset

xarray.Dataset: GLOW model output in GEO coordinates.

Source code in glow2d/_glow2d.py
def run_model(self) -> xarray.Dataset:
    """Run the GLOW model calculation to get the model output in GEO coordinates.

    Returns:
        xarray.Dataset: GLOW model output in GEO coordinates.
    """
    if self._bds is not None:
        return self._bds
    # calculate the GLOW model for each lat-lon point determined in init()
    bds = self._calc_glow_noprecip()
    unit_desc_dict = {
        'angle': ('radians', 'Angle of location w.r.t. radius vector at origin (starting location)'),
        'lat': ('degree', 'Latitude of locations'),
        'lon': ('degree', 'Longitude of location')
    }
    _ = list(map(lambda x: bds[x].attrs.update(
        {'units': unit_desc_dict[x][0], 'description': unit_desc_dict[x][1]}), unit_desc_dict.keys()))
    self._bds = bds
    return self._bds  # return the calculated