FAC: Field-aligned currents#

The FAC toolbox provides the ability to calculate FACs on-demand, given magnetic measurements and model predictions.

Currently, the single-satellite algorithm is implemented and provides very similar output as the operational product SW_FACxTMS_2F. The results are not identical because of differences in the processing chain (for example, using the POMME model instead of the CHAOS model to supply the background magnetic field, or “mean field”)

For a description of the method, see:
Ritter, P., Lühr, H. & Rauberg, J. Determining field-aligned currents with the Swarm constellation mission. Earth Planet Sp 65, 1285–1294 (2013). https://doi.org/10.5047/eps.2013.09.006

For more sophisticated FAC estimates, see https://github.com/ablagau/SwarmFACE

import datetime as dt
import numpy as np
import matplotlib.pyplot as plt

from swarmpal.io import create_paldata, PalDataItem
from swarmpal.toolboxes import fac

Fetching data#

data_params = dict(
    collection="SW_OPER_MAGA_LR_1B",
    measurements=["B_NEC", "Flags_F", "Flags_B", "Flags_q"],
    models=["CHAOS"],
    start_time="2016-01-01T00:00:00",
    end_time="2016-01-01T03:00:00",
    server_url="https://vires.services/ows",
    options=dict(asynchronous=False, show_progress=False),
)

data = create_paldata(PalDataItem.from_vires(**data_params))
print(data)
<xarray.DataTree 'paldata'>
Group: /
└── Group: /SW_OPER_MAGA_LR_1B
        Dimensions:      (Timestamp: 10800, NEC: 3)
        Coordinates:
          * Timestamp    (Timestamp) datetime64[s] 86kB 2016-01-01 ... 2016-01-01T02:...
          * NEC          (NEC) <U1 12B 'N' 'E' 'C'
        Data variables:
            Spacecraft   (Timestamp) object 86kB 'A' 'A' 'A' 'A' 'A' ... 'A' 'A' 'A' 'A'
            Latitude     (Timestamp) float64 86kB -72.5 -72.56 -72.63 ... -44.97 -45.03
            Longitude    (Timestamp) float64 86kB 92.79 92.82 92.85 ... 41.83 41.83
            Flags_q      (Timestamp) uint8 11kB 5 5 5 5 5 5 5 5 5 ... 5 5 5 5 5 5 5 5 5
            Flags_F      (Timestamp) uint8 11kB 1 1 1 1 1 1 1 1 1 ... 1 1 1 1 1 1 1 1 1
            B_NEC_CHAOS  (Timestamp, NEC) float64 259kB -1.602e+03 ... -2.568e+04
            Radius       (Timestamp) float64 86kB 6.834e+06 6.834e+06 ... 6.833e+06
            Flags_B      (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
            B_NEC        (Timestamp, NEC) float64 259kB -1.581e+03 ... -2.564e+04
        Attributes:
            Sources:         ['CHAOS-8.1_static.shc', 'SW_OPER_MAGA_LR_1B_20151231T00...
            MagneticModels:  ["CHAOS = 'CHAOS-Core'(max_degree=20,min_degree=1) + 'CH...
            AppliedFilters:  []
            PAL_meta:        {"analysis_window": ["2016-01-01T00:00:00", "2016-01-01T...
data.swarmpal.pal_meta
{'.': {},
 'SW_OPER_MAGA_LR_1B': {'analysis_window': ['2016-01-01T00:00:00',
   '2016-01-01T03:00:00'],
  'magnetic_models': {'CHAOS': "'CHAOS-Core'(max_degree=20,min_degree=1) + 'CHAOS-Static'(max_degree=185,min_degree=21) + 'CHAOS-MMA-Primary'(max_degree=2,min_degree=1) + 'CHAOS-MMA-Secondary'(max_degree=2,min_degree=1)"},
  'config': {'pad_times': [],
   'collection': 'SW_OPER_MAGA_LR_1B',
   'measurements': ['B_NEC', 'Flags_F', 'Flags_B', 'Flags_q'],
   'start_time': '2016-01-01T00:00:00',
   'end_time': '2016-01-01T03:00:00',
   'server_url': 'https://vires.services/ows',
   'models': ['CHAOS'],
   'auxiliaries': [],
   'sampling_step': None,
   'filters': [],
   'options': {'asynchronous': False, 'show_progress': False},
   'provider': 'vires'}}}

Applying a process and viewing the results#

process = fac.processes.FAC_single_sat(
    config={
        "dataset": "SW_OPER_MAGA_LR_1B",
        "model_varname": "B_NEC_CHAOS",
        "measurement_varname": "B_NEC",
    },
)
data = process(data)
print(data)
<xarray.DataTree 'paldata'>
Group: /
│   Attributes:
│       PAL_meta:  {"output_datasets": ["PAL_FAC_single_sat"]}
├── Group: /SW_OPER_MAGA_LR_1B
│       Dimensions:      (Timestamp: 10800, NEC: 3)
│       Coordinates:
│         * Timestamp    (Timestamp) datetime64[s] 86kB 2016-01-01 ... 2016-01-01T02:...
│         * NEC          (NEC) <U1 12B 'N' 'E' 'C'
│       Data variables:
│           Spacecraft   (Timestamp) object 86kB 'A' 'A' 'A' 'A' 'A' ... 'A' 'A' 'A' 'A'
│           Latitude     (Timestamp) float64 86kB -72.5 -72.56 -72.63 ... -44.97 -45.03
│           Longitude    (Timestamp) float64 86kB 92.79 92.82 92.85 ... 41.83 41.83
│           Flags_q      (Timestamp) uint8 11kB 5 5 5 5 5 5 5 5 5 ... 5 5 5 5 5 5 5 5 5
│           Flags_F      (Timestamp) uint8 11kB 1 1 1 1 1 1 1 1 1 ... 1 1 1 1 1 1 1 1 1
│           B_NEC_CHAOS  (Timestamp, NEC) float64 259kB -1.602e+03 ... -2.568e+04
│           Radius       (Timestamp) float64 86kB 6.834e+06 6.834e+06 ... 6.833e+06
│           Flags_B      (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
│           B_NEC        (Timestamp, NEC) float64 259kB -1.581e+03 ... -2.564e+04
│       Attributes:
│           Sources:         ['CHAOS-8.1_static.shc', 'SW_OPER_MAGA_LR_1B_20151231T00...
│           MagneticModels:  ["CHAOS = 'CHAOS-Core'(max_degree=20,min_degree=1) + 'CH...
│           AppliedFilters:  []
│           PAL_meta:        {"analysis_window": ["2016-01-01T00:00:00", "2016-01-01T...
└── Group: /PAL_FAC_single_sat
        Dimensions:    (Timestamp: 10799)
        Coordinates:
          * Timestamp  (Timestamp) datetime64[ns] 86kB 2016-01-01T00:00:00.500000 ......
        Data variables:
            FAC        (Timestamp) float64 86kB -0.1344 -0.04132 ... 0.005725 -0.007201
            IRC        (Timestamp) float64 86kB -0.131 -0.04028 ... 0.005101 -0.006416
            Latitude   (Timestamp) float64 86kB nan nan nan nan nan ... nan nan nan nan
            Longitude  (Timestamp) float64 86kB nan nan nan nan nan ... nan nan nan nan
            Flags_q    (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
            Flags_F    (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
            Radius     (Timestamp) float64 86kB nan nan nan nan nan ... nan nan nan nan
            Flags_B    (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
        Attributes:
            Sources:   ['CHAOS-8.1_static.shc', 'SW_OPER_MAGA_LR_1B_20151231T000000_2...
            PAL_meta:  {"FAC_single_sat": {"output_dataset": "PAL_FAC_single_sat", "d...
/home/docs/checkouts/readthedocs.org/user_builds/swarmpal/envs/stable/lib/python3.11/site-packages/xarray/core/duck_array_ops.py:264: RuntimeWarning: invalid value encountered in cast
  return data.astype(dtype, **kwargs)
data.swarmpal.pal_meta
{'.': {'output_datasets': ['PAL_FAC_single_sat']},
 'SW_OPER_MAGA_LR_1B': {'analysis_window': ['2016-01-01T00:00:00',
   '2016-01-01T03:00:00'],
  'magnetic_models': {'CHAOS': "'CHAOS-Core'(max_degree=20,min_degree=1) + 'CHAOS-Static'(max_degree=185,min_degree=21) + 'CHAOS-MMA-Primary'(max_degree=2,min_degree=1) + 'CHAOS-MMA-Secondary'(max_degree=2,min_degree=1)"},
  'config': {'pad_times': [],
   'collection': 'SW_OPER_MAGA_LR_1B',
   'measurements': ['B_NEC', 'Flags_F', 'Flags_B', 'Flags_q'],
   'start_time': '2016-01-01T00:00:00',
   'end_time': '2016-01-01T03:00:00',
   'server_url': 'https://vires.services/ows',
   'models': ['CHAOS'],
   'auxiliaries': [],
   'sampling_step': None,
   'filters': [],
   'options': {'asynchronous': False, 'show_progress': False},
   'provider': 'vires'}},
 'PAL_FAC_single_sat': {'FAC_single_sat': {'output_dataset': 'PAL_FAC_single_sat',
   'dataset': 'SW_OPER_MAGA_LR_1B',
   'model_varname': 'B_NEC_CHAOS',
   'measurement_varname': 'B_NEC',
   'inclination_limit': 30,
   'time_jump_limit': 1,
   'include_auxiliaries': True}}}
print(data["PAL_FAC_single_sat"])
<xarray.DataTree 'PAL_FAC_single_sat'>
Group: /PAL_FAC_single_sat
    Dimensions:    (Timestamp: 10799)
    Coordinates:
      * Timestamp  (Timestamp) datetime64[ns] 86kB 2016-01-01T00:00:00.500000 ......
    Data variables:
        FAC        (Timestamp) float64 86kB -0.1344 -0.04132 ... 0.005725 -0.007201
        IRC        (Timestamp) float64 86kB -0.131 -0.04028 ... 0.005101 -0.006416
        Latitude   (Timestamp) float64 86kB nan nan nan nan nan ... nan nan nan nan
        Longitude  (Timestamp) float64 86kB nan nan nan nan nan ... nan nan nan nan
        Flags_q    (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
        Flags_F    (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
        Radius     (Timestamp) float64 86kB nan nan nan nan nan ... nan nan nan nan
        Flags_B    (Timestamp) uint8 11kB 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
    Attributes:
        Sources:   ['CHAOS-8.1_static.shc', 'SW_OPER_MAGA_LR_1B_20151231T000000_2...
        PAL_meta:  {"FAC_single_sat": {"output_dataset": "PAL_FAC_single_sat", "d...
data.swarmpal_fac.quicklook();
../../_images/645d2a4591a0199236967c5987a51e8ef5abfeadc86bc0911e091ad25d098bb3.png

Retrying with data subselection#

This time we will fetch data with a filter applied to the request from VirES

See viresclient.SwarmRequest.add_filter for how these behave. swarmpal.io.PalDataItem.from_vires accepts a list of such filters.

NB. there is currently a bug requiring that each filter string is enclosed in parentheses.

data_params = dict(
    collection="SW_OPER_MAGA_LR_1B",
    measurements=["B_NEC"],
    models=["CHAOS"],
    start_time="2016-01-01T00:00:00",
    end_time="2016-01-01T03:00:00",
    server_url="https://vires.services/ows",
    options=dict(asynchronous=False, show_progress=False),
    filters=[
        "((QDLat > 50) OR (QDLat < -50))",  # the algorithm is only valid at high latitude
        "(Flags_B <= 9)",  # Exclude particularly bad data
    ],
)

data = create_paldata(PalDataItem.from_vires(**data_params))
data = data.swarmpal.apply(process)
data.swarmpal_fac.quicklook();
../../_images/084eac077c33e90731ff4dc04d02369ad1a24b2a1b7f9e35a88ed2e47f2e83c3.png

Comparing with FAC product#

# Fetch the input to the FAC process...
data_params_fac_input = dict(
    collection="SW_OPER_MAGA_LR_1B",
    measurements=["B_NEC"],
    models=["CHAOS"],
    start_time="2016-01-01T00:00:00",
    end_time="2016-01-01T03:00:00",
    server_url="https://vires.services/ows",
    options=dict(asynchronous=False, show_progress=False),
)
# ... and the FAC product itself
data_params_fac_product = dict(
    collection="SW_OPER_FACATMS_2F",
    measurements=["IRC", "FAC"],
    start_time="2016-01-01T00:00:00",
    end_time="2016-01-01T03:00:00",
    server_url="https://vires.services/ows",
    options=dict(asynchronous=False, show_progress=False),
)
data = create_paldata(
    PalDataItem.from_vires(**data_params_fac_input),
    PalDataItem.from_vires(**data_params_fac_product),
)

# Apply the FAC process
process = fac.processes.FAC_single_sat(
    config={
        "dataset": "SW_OPER_MAGA_LR_1B",
        "model_varname": "B_NEC_CHAOS",
        "measurement_varname": "B_NEC",
    },
)
data = process(data)
# Plot comparing them
fig, axes = plt.subplots(nrows=3, figsize=(15, 10), sharex=True)
data["PAL_FAC_single_sat"]["IRC"].plot(ax=axes[0])
data["SW_OPER_FACATMS_2F"]["IRC"].plot(ax=axes[1])
(data["PAL_FAC_single_sat"]["IRC"] - data["SW_OPER_FACATMS_2F"]["IRC"]).plot(ax=axes[2])
axes[0].set_ylabel(f"SwarmPAL on-demand\n{axes[0].get_ylabel()}")
axes[1].set_ylabel(f"Official product\n{axes[1].get_ylabel()}")
axes[2].set_ylabel("Difference between the above")
for ax in axes:
    ax.grid()
    ax.set_xlabel("")
../../_images/87b8fbb0b3884e6a0d120e5d3666c302327474eda828149607df216f400e7f86.png