C++ Module: spaceWeatherData
Executive Summary
The spaceWeatherData module reads a CelesTrak-style space-weather CSV table and publishes the
23 SwDataMsgPayload output messages required by C++ Module: msisAtmosphere. On every simulation
update step the module determines the current UTC calendar date and time from the linked
EpochMsgPayload, looks up the corresponding rows in the loaded table, and assembles the
NRLMSISE-00 / NRLMSIS 2.0 space-weather index vector before writing it to its output message array.
Message Connection Descriptions
The following table lists all the module input and output messages. The module msg connection is set by the user from Python. The msg type contains a link to the message structure definition, while the description provides information on what this message is used for.
Msg Variable Name |
Msg Type |
Description |
|---|---|---|
epochInMsg |
Simulation start epoch in UTC (year, month, day, hours, minutes, seconds). Must be linked before |
|
swDataOutMsgs[0] |
Daily Ap average ( |
|
swDataOutMsgs[1..20] |
Twenty consecutive 3-hour Ap values stepping back from the current bin — MSIS channels |
|
swDataOutMsgs[21] |
Centered 81-day observed F10.7 of the current day ( |
|
swDataOutMsgs[22] |
Observed daily F10.7 of the previous day ( |
Note
swDataOutMsgs is a std::vector of heap-allocated Message<SwDataMsgPayload>
pointers initialised to length 23 in the constructor. In Python the vector elements are
accessed by index: swModule.swDataOutMsgs[i].
Detailed Module Description
The spaceWeatherData module converts a Gregorian calendar date (year, month, day) to a signed
Unix day number. The resulting integer counts whole days since the Unix epoch (1970-01-01 = 0)
and is used as the unique key when looking up rows in the loaded weather table.
On every call to UpdateState the module performs the following steps to determine the current
UTC calendar date:
Read EpochMsgPayload (
year,month,day,hours,minutes,seconds).Compute \(D_{\text{epoch}}\) from the calendar date.
Compute \(t_{\text{epoch}}\) from the time-of-day fields.
Add the simulation time: \(T_{\text{total}} = t_{\text{epoch}} + t_{\text{sim}}\).
Extract the whole-day offset \(\Delta D\) and residual \(s_{\text{day}}\).
Determine the current day number \(D_0 = D_{\text{epoch}} + \Delta D\).
Determine the current 3-hour Ap bin \(b_{\text{cur}} = \lfloor s_{\text{day}} / 10800 \rfloor\).
The table below defines the symbols used above.
Symbol |
Unit |
Description |
|---|---|---|
\(t_{\text{sim}}\) |
s |
Simulation time elapsed since the BSK epoch ( |
\(t_{\text{epoch}}\) |
s |
UTC seconds-of-day of the epoch (hour x 3600 + minute x 60 + second). |
\(D_{\text{epoch}}\) |
day |
Unix day number of the epoch date (days since 1970-01-01). |
\(T_{\text{total}}\) |
s |
Total UTC seconds elapsed from the start of the epoch calendar day: \(T_{\text{total}} = t_{\text{epoch}} + t_{\text{sim}}\). |
\(\Delta D\) |
day |
Whole-day offset from the epoch date: \(\Delta D = \lfloor T_{\text{total}} / 86400 \rfloor\). |
\(s_{\text{day}}\) |
s |
Seconds elapsed since midnight of the current UTC day: \(s_{\text{day}} = T_{\text{total}} - \Delta D \times 86400\). |
\(D_{0}\) |
day |
Unix day number of the current UTC date: \(D_{0} = D_{\text{epoch}} + \Delta D\). |
\(b_{\text{cur}}\) |
— |
Index of the current 3-hour Ap bin (0-based): \(b_{\text{cur}} = \lfloor s_{\text{day}} / 3600 / 3 \rfloor\). |
The 23 output channels map directly to the sw array consumed by the msisAtmosphere module.
The mapping implemented by computeSwState is:
Channel |
MSIS label |
Source column and day |
|---|---|---|
|
|
|
|
|
3-hour Ap at bin \(b_{\text{cur}}\) of day \(D_0\) |
|
|
3-hour Ap at bin \(b_{\text{cur}} - 1\), wrapping to \(D_{-1}\) as needed |
|
|
3-hour Ap at bin \(b_{\text{cur}} - 2\), wrapping to \(D_{-1}\) or \(D_{-2}\) |
… (pattern continues) |
… |
Each subsequent channel steps back one 3-hour bin, wrapping across day boundaries up to \(D_{-3}\) |
|
|
3-hour Ap at bin \(b_{\text{cur}} - 19\), up to day \(D_{-3}\) |
|
|
|
|
|
|
The 3-hour Ap look-back (channels 1-20) is computed starting from the current 3-hour bin \(b_{\text{cur}}\) within day \(D_0\). The module subtracts one bin index per channel. Whenever the bin index drops below zero it is incremented by 8 (the number of 3-hour bins per day) and the day offset is advanced by one. If the resulting day offset exceeds 3 the look-up fails and the output retains the last published values.
loadSpaceWeatherFile performs the following actions in order:
Open the file; log an error and return if the file cannot be read.
Parse the header row and verify that all required column names are present. Log an error and return for any missing column.
Parse each subsequent non-empty line into a
DailySpaceWeatherrecord. Stop at the first line that fails to parse (see assumption 4 in Module Assumptions and Limitations).Verify that the resulting list contains at least one valid row.
Verify that
DATEvalues are strictly ascending and contain no duplicates.If all checks pass, store the validated list and record the loaded file path.
If any validation step fails a BSK_ERROR is thrown.
During Reset the module probes whether the simulation start date is covered by the loaded
table; a BSK_ERROR is emitted immediately if it is not, rather than failing silently at run
time. A zero-state is always published at Reset.
If the current simulation date (or any of the three preceding days) is missing from the table
the module logs a warning and keeps publishing the last available data.
This can happen only during the simulation, because the first epoch is tested at Reset.
Module Assumptions and Limitations
Required columns. The input CSV must contain a header row with at least the following column names (additional columns are ignored):
DATE,AP1,AP2,AP3,AP4,AP5,AP6,AP7,AP8,AP_AVG,F10.7_OBS,F10.7_OBS_CENTER81Date format. Every
DATEcell must follow theYYYY-MM-DDformat. Month values must be in [1, 12] and day values in [1, 31]; otherwise the row is treated as invalid.Table ordering and uniqueness.
DATEvalues must be strictly ascending and free of duplicates. The loader returns an error and discards the entire file if either condition is violated.Early termination on invalid rows. The file parser stops reading at the first row that cannot be fully parsed (missing, non-numeric, or numeric fields with trailing characters in AP or F10.7 columns, or a
DATEtoken with trailing characters). This is intentional: the monthly predicted section at the end of CelesTrak files omits AP values and should not be ingested.Run-time look-up window. At each update step the module requires four consecutive calendar days to be present in the table: the current day, and the three preceding days. If any of these four entries is absent the module outputs the preceding state and logs a warning.
Epoch is UTC. The simulation epoch provided through EpochMsgPayload is interpreted as UTC. No leap-second or timezone correction is applied.
Three-hour AP bins. The table stores exactly 8 three-hour Ap indices per day (
AP1…AP8), corresponding to the 00-03 UT, 03-06 UT, … 21-24 UT intervals. The module derives the current 3-hour bin from the fractional UTC time of day.No interpolation. All space-weather indices are taken directly from the table; no interpolation between rows is performed.
User Guide
Obtaining a space-weather file
Download the historical + short-range predicted file directly from CelesTrak:
https://celestrak.org/SpaceData/SW-Last5Years.csv
The file covers the past five years of observed data followed by a short predicted section. Because the predicted rows omit AP values, the module’s early-termination parser automatically stops before ingesting them.
For simulations that extend beyond the five-year window, use the complete historical archive:
https://celestrak.org/SpaceData/SW-All.csv
Basic Setup
The minimal Python setup connects the epoch message, loads the CSV file, and subscribes each
msisAtmosphere input to the corresponding output:
from Basilisk.simulation import msisAtmosphere, spaceWeatherData
from Basilisk.utilities import unitTestSupport, SimulationBaseClass
scSim = SimulationBaseClass.SimBaseClass()
dynProcess = scSim.CreateNewProcess("DynamicsProcess")
dynTaskName = "DynamicsTask"
dynProcess.addTask(scSim.CreateNewTask(dynTaskName, macros.sec2nano(10.0)))
# --- Space-weather module ---
swModule = spaceWeatherData.SpaceWeatherData()
swModule.ModelTag = "spaceWeatherData"
swModule.loadSpaceWeatherFile("SW-Last5Years.csv")
# Connect the simulation epoch
timeInitString = "2026 March 04 21:48:00.000000"
epochMsg = unitTestSupport.timeStringToGregorianUTCMsg(timeInitString)
swModule.epochInMsg.subscribeTo(epochMsg)
# --- Atmosphere module ---
atmoModule = msisAtmosphere.MsisAtmosphere()
atmoModule.epochInMsg.subscribeTo(epochMsg)
atmoModule.ModelTag = "msisAtmosphere"
for msgIndex in range(23):
atmoModule.swDataInMsgs[msgIndex].subscribeTo(swModule.swDataOutMsgs[msgIndex])
scSim.AddModelToTask(dynTaskName, swModule)
scSim.AddModelToTask(dynTaskName, atmoModule)
Note
swModule must be added to the task before atmoModule, or have a higher priority,
so that the output messages are written before the atmosphere module reads them within the
same task step.
Choosing the Correct Epoch
The epoch message must represent the UTC date and time at which the simulation clock reads zero. The module adds the BSK simulation time (in seconds) to this epoch to obtain the current UTC instant. Providing a wrong epoch will shift all space-weather indices in time and may cause look-up failures if the resulting date falls outside the loaded table.
# Epoch at J2000.0 (2000-01-01 11:58:55.816 UTC)
epochMsg = unitTestSupport.timeStringToGregorianUTCMsg("2000 January 01 11:58:55.816000")
swModule.epochInMsg.subscribeTo(epochMsg)
Error Conditions and Mitigation
Symptom |
Likely cause and remedy |
|---|---|
|
|
|
|
|
The epoch resolves to a calendar date for which the module cannot assemble a full look-back window (current day plus three preceding days). Verify the epoch is correct and that the loaded CSV file covers the simulation start date. |
|
The CSV header does not contain one of the mandatory column names. Ensure the file was downloaded from CelesTrak and has not been manually edited. |
|
Two rows share the same |
|
Rows are not in ascending date order. Sort the file by date and reload. |
|
Every data row failed to parse, leaving the table empty. Check that the CSV is a valid CelesTrak file and that at least the first data row is well-formed. |
|
The current simulation date or one of the three preceding days is absent from the table. Use a longer or more recent CSV file so that the look-up window falls within the loaded range. |
|
A data row could not be parsed (malformed |
-
class SpaceWeatherData : public SysModel
- #include <spaceWeatherData.h>
CelesTrak space-weather data publisher for :ref:
msisAtmosphere.Public Functions
-
SpaceWeatherData()
Constructor. Allocates the 23 heap-owned output messages and pushes their pointers into the swDataOutMsgs vector.
-
~SpaceWeatherData()
Destructor. Frees every output message owned by this module.
-
SpaceWeatherData(const SpaceWeatherData&) = delete
-
SpaceWeatherData &operator=(const SpaceWeatherData&) = delete
-
void Reset(uint64_t CurrentSimNanos)
Reset the module to its initial state.
Verifies that epochInMsg is linked and that the space-weather table has been loaded. Probes whether the simulation start date is covered by the loaded table and emits a BSK_ERROR immediately if it is not. Publishes a zero-state message.
- Parameters:
CurrentSimNanos – Current simulation time in nanoseconds.
-
void UpdateState(uint64_t CurrentSimNanos)
Update the module output messages for the current simulation time.
Resolves the current UTC calendar date from the epoch message and elapsed simulation time, assembles the 23-element NRLMSISE-00 / NRLMSIS 2.0 space-weather index vector, and writes each element to the corresponding output message. If the required look-up window is not available in the loaded table a BSK_WARNING is logged and the previous output is retained.
- Parameters:
CurrentSimNanos – Current simulation time in nanoseconds.
-
void loadSpaceWeatherFile(const std::string &fileName)
Load and validate a CelesTrak-style space-weather CSV file.
Opens the file, parses the header row, reads data rows into DailySpaceWeather records until the first unparsable line, and validates that DATE values are strictly ascending with no duplicates. On success the internal weather table is replaced with the new data. On any failure a BSK_ERROR is logged and the previously loaded table (if any) is left intact.
- Parameters:
fileName – Path to the space-weather CSV file (absolute or relative).
Public Members
-
ReadFunctor<EpochMsgPayload> epochInMsg = {}
epoch input message
-
std::vector<Message<SwDataMsgPayload>*> swDataOutMsgs = {}
23-element MSIS SW output message vector
-
BSKLogger bskLogger = {}
BSK logging helper.
Private Functions
-
void publishZeroState(uint64_t CurrentSimNanos)
Write a zero-valued payload to every output message.
Called at the end of Reset() so that downstream modules always receive at least one valid message before the first UpdateState().
- Parameters:
CurrentSimNanos – Current simulation time in nanoseconds.
-
bool computeSwState(uint64_t CurrentSimNanos, std::array<double, numSwMessages> &swState)
Assemble the 23-element NRLMSISE-00 / NRLMSIS 2.0 space-weather state vector.
Resolves the current UTC date and 3-hour Ap bin, retrieves the four required consecutive calendar-day records from the in-memory table, and fills the output array according to the channel mapping described in the module documentation.
- Parameters:
CurrentSimNanos – Current simulation time in nanoseconds.
swState – [out] 23-element array to be populated with space-weather index values.
- Returns:
True if all required table entries were found and the state was assembled successfully; false if any required day is missing or the table is empty.
-
void getEpochState(double CurrentSimSeconds, int64_t ¤tDay, double &secondsOfDay)
Determine the current Unix day number and seconds-of-day from the epoch message and elapsed simulation time.
Reads the EpochMsgPayload, converts the calendar date to a Unix day number, adds the epoch time-of-day and the simulation elapsed time, and decomposes the result into a whole-day count and a residual seconds-of-day value.
- Parameters:
CurrentSimSeconds – Simulation time elapsed since BSK epoch, in seconds.
currentDay – [out] Unix day number of the current UTC date (days since 1970-01-01).
secondsOfDay – [out] Elapsed seconds since midnight of the current UTC day [0, 86400).
Private Members
-
std::vector<DailySpaceWeather> weatherData = {}
parsed weather table
-
std::string loadedFileName = {}
loaded CSV path
Private Static Functions
-
static int64_t civilToUnixDayNumber(int year, unsigned month, unsigned day)
Convert a proleptic Gregorian calendar date to a signed Unix day number.
Computes the number of whole days elapsed since 1970-01-01 (which maps to day number 0). The result is negative for dates before the Unix epoch.
- Parameters:
year – Gregorian year (may be negative).
month – Month of year in the range [1, 12].
day – Day of month; valid range is [1, 28], [1, 29], [1, 30], or [1, 31] depending on the month and whether year is a leap year.
- Returns:
Signed count of days since 1970-01-01.
-
static DailySpaceWeather parseWeatherLine(const std::string &line, const std::vector<std::string> &headerColumns, bool &parseOk)
Parse one data line of the space-weather CSV into a DailySpaceWeather record.
Splits the line on commas, extracts the DATE field and all required numeric columns, and populates the output record. Sets parseOk to false and returns a default-constructed record if any field is missing, empty, or non-numeric.
- Parameters:
line – One non-empty data line from the CSV file (no header).
headerColumns – Ordered list of column name strings from the CSV header row.
parseOk – [out] Set to true when all required fields were parsed successfully; false otherwise.
- Returns:
Populated DailySpaceWeather record on success, or a default-constructed record on failure.
Private Static Attributes
-
static uint64_t numSwMessages = 23U
[count] number of MSIS weather channels
-
static uint64_t numApPerDay = 8U
[count] number of three-hour Ap entries per day
-
struct DailySpaceWeather
-
SpaceWeatherData()