Finding a GOES class proxy for STIX¶

What is the relationship between peak counts in STIX flares and GOES class?

c.f. Lastufka et al 2020b

Problem description¶

GOES class is the Richter scale for solar flares; it is determined from the peak flux in the GOES 1-8 Å (long wavelength) channel during a X-ray flare event.

GOES 1-minute Xray flux from Feb 5 2022

GOES X-ray flux live view

GOES is in geostationary orbit and therefore has a constant, full-disk view of only the earth-facing side of the Sun. Solar Orbiter orbits the Sun, meaning X-ray flares observed by STIX will not always be observed with GOES.

Solar Orbiter orbit from the last year

Orbit tool live view

Instrument details¶

  • GOES X-ray Sensor (XRS)

    • 2 channels, < 1 minute time resolution
    • full-disk integrated flux (W m$^{-2}$)
      • Solar flare location must be determined from other instruments!
    • In the past 2 years, GOES 15,16, and 17 operational
    • Occasional <60 minute data dropouts due to eclipse season (45 - 60 days)
  • Solar Orbiter STIX

    • X-ray indirect imaging spectrometer, < 1 minute time resolution
    • Energy range from 4-100 keV
      • flare list peak counts/s in the 4-10 keV channel
    • full-disk view or spacecraft pointing
    • Current imaging accuracy ~ 10" source position with aspect solution
      • 60" without aspect solution (distance from Sun > .75 AU)
  • Solar Dynamics Observatory AIA
    • full-disk EUV/UV imaging in 9 channels, .6" spatial resolution (4K), 12s time resolution
    • bright EUV flare ribbons can correspond spatially to X-ray flare footpoint sources

Workflow:¶

  • Start with STIX flare list
    • correct event times for light travel time
    • correct background-subtracted peak counts/s to 1 AU distance
  • Find AIA flares with location that occur within 10 minutes of STIX flare
  • Calculate if AIA flares are visible from STIX
    • via coordinate transformation
  • For AIA flares with a GOES class, model the relationship with linear regression
  • Calculate 1-sigma errors to give upper and lower boundary GOES classes predicted for flares not visible from Earth
  • Implement GOES class prediction in STIX Data Center
    • Automate integration new events into fit
    • Execute twice a week with usual satellite data dump




Data collection & preparation¶

  • STIX flare list from STIX data center (stixdcpy)
  • HEK events from HEK database (sunpy Fido)
  • Spacecraft trajectory information from AIA fits headers (sunpy drms) and Solar Orbiter SPICE kernels
def stix_hek_data(start_time=None,end_time=None,lighttime_correct=True,local_copy=False):
    '''Get STIX flare list for given time period. Search HEK/AIA database for flares occuring within +- 5 minutes from specified time. Perform coordinate transformation. 
    To be implemented: Reverse joint visibility calculation (is STIX flare visible from Earth) once CFL locations are returned by stixdcpy'''
    if not start_time:
        start_time=dt(2020,2,10,5,0,0)
    if not end_time:
        end_time=dt.now()
    load_SOLO_SPICE(dt.now()) #SPICE kernels for Solar Orbiter trajectory
    stix_flare_list=get_stix_flares(start_time,end_time,sortedby='time',lighttime_correct=True)
    stix_flare_list['peak_utc']=pd.to_datetime(stix_flare_list.peak_UTC)
    #stix_flare_list['peak_utc']=[d.replace(microsecond=0) for d in stix_flare_list.peak_utc]
    nflares=stix_flare_list.peak_utc.count()
    log.info(f"{nflares} STIX flares found")
    if nflares == 1000:
        log.warning(f"{nflares} STIX flares found, there might be more results in the specified date range!")
    #is there a HEK event at same time?
    hek_events=[hek.HEKEventHandler(f.peak_utc_corrected).df for _,f in stix_flare_list.iterrows()]
    log.info(f"{len(hek_events)} HEK results found")
    hdf=pd.concat(hek_events)
    hdf['event_peaktime']=pd.to_datetime(hdf.event_peaktime)
    #merge STIX results with HEK results
    df=stix_flare_list.merge(hdf,left_on='peak_utc_corrected',right_on='date_obs',how='outer')
    return df

def get_stix_flares(start_time,end_time,sortedby='time',lighttime_correct=True,local_copy=True):
    '''Query STIX data center for flare list. Perform light travel time correction.'''
    flares=jreq.fetch_flare_list(start_time,end_time,sortedby=sortedby) #current limit is 1000, results are returned backwards from end time
    df=pd.DataFrame(flares)
    corrected_times,solo_r=[],[]
    if lighttime_correct:
        for d in df.peak_UTC:
            ctime,solor=spacecraft_to_earth_time(d,solo_r=True)
            corrected_times.append(ctime)
            solo_r.append(solor)
        df['peak_utc_corrected']=corrected_times
        df['solo_r']=solo_r
        df['peak_counts_corrected']=df.peak_counts*(df.solo_r)**2 #correct to counts @ 1AU
    if local_copy:
        df.to_json(f"/home/erica/STIX/data/stix_flares_{dt.strftime(dt.now(),'%Y-%m-%dT%H:%M')}.json")
    return df

HEKEventHandler gets and formats results from AIA flare catalog. It also gets the Solar Orbiter trajectory information and uses this to transform the AIA coordinates to the Solar Orbiter coordinate system via _rotatecoord

rotate_coord takes a coordinate or list of coordinates and rotates it, providing output in units relevant to different applications




Putting it all together to update results¶

def update_stix_hek_data(current_data_file='/home/erica/STIX/data/stix_hek_full.json'):
    '''update the csv file with the latest flares from STIX and AIA'''
    df=pd.read_json(current_data_file)
    df['peak_utc']=pd.to_datetime(df.peak_utc,unit='ms')
    last_date=df.peak_utc.iloc[0] #newest events are added at the beginning
    newdf=stix_hek_data(start_time=last_date)
    udf=pd.concat([newdf,df])
    udf.drop_duplicates(subset=[k for k in udf.keys() if k !='goes'],inplace=True)
    udf.reset_index(inplace=True,drop=True)
    udf.to_json(current_data_file)
df.describe()
flare_id total_signal_counts total_counts duration LC0_BKG _id GOES_flux CFL_X_arcsec CFL_Y_arcsec peak_counts ... rotated_y_arcsec rotated_lon_deg rotated_lat_deg rotated_x_px rotated_y_px visible_from_SOLO start_unix end_unix STIX_AIA_timedelta STIX_AIA_timedelta_abs
count 3.423000e+03 3.423000e+03 4.720000e+02 3423.000000 472.000000 3423.000000 4.720000e+02 0.0 0.0 3423.000000 ... 1988.000000 1988.000000 1988.000000 1988.000000 1988.000000 2023.000000 2.951000e+03 2.951000e+03 2023 2023
mean 2.125082e+09 4.735241e+05 5.950362e+05 857.280748 279.135593 2305.458662 1.362252e-06 NaN NaN 3845.376629 ... -93.811257 14.986685 -6.209649 2273.108068 1953.687093 0.867523 1.632102e+09 1.632102e+09 0 days 00:05:20.429510133 0 days 00:20:13.049021749
std 3.496230e+07 5.462422e+06 3.765789e+06 1062.892194 53.964668 991.701731 2.980354e-06 NaN NaN 27491.654699 ... 462.247365 58.579077 34.129521 772.867709 462.252368 0.339092 6.282455e+06 6.282389e+06 0 days 01:15:44.329653006 0 days 01:13:11.063900368
min 2.104240e+09 6.400000e+01 7.929000e+03 80.000000 247.000000 589.000000 0.000000e+00 NaN NaN 262.466667 ... -1093.814195 -87.407210 -89.177735 836.053013 953.675551 0.000000 1.619226e+09 1.619228e+09 -1 days +08:11:16.071000 0 days 00:00:00.286000
25% 2.108201e+09 4.384000e+03 3.555250e+04 348.000000 247.000000 1444.500000 5.519000e-07 NaN NaN 337.133333 ... -495.312198 -40.325495 -37.766042 1570.362438 1552.186403 1.000000 1.627829e+09 1.627829e+09 0 days 00:00:50.007500 0 days 00:02:38.810500
50% 2.110041e+09 1.126400e+04 6.639950e+04 584.000000 271.000000 2307.000000 8.603500e-07 NaN NaN 441.666667 ... -293.295623 22.147903 -25.045989 2354.270866 1754.201461 1.000000 1.632336e+09 1.632337e+09 0 days 00:04:18.543000 0 days 00:05:43.435000
75% 2.112212e+09 4.230800e+04 1.402060e+05 1030.000000 271.000000 3162.500000 1.220925e-06 NaN NaN 892.866667 ... 344.011934 73.784082 28.810110 2930.122439 2391.515049 1.000000 1.636705e+09 1.636708e+09 0 days 00:07:07.060500 0 days 00:08:18.781000
max 2.202022e+09 2.089835e+08 5.447654e+07 35680.000000 463.000000 4028.000000 5.508880e-05 NaN NaN 753663.000000 ... 822.923871 89.111706 54.493623 3613.201130 2870.438193 1.000000 1.642118e+09 1.642119e+09 0 days 14:13:33.318000 0 days 15:48:43.929000

8 rows × 36 columns

tplus=td(minutes=10)
dfs=df.query("visible_from_SOLO==True and STIX_AIA_timedelta_abs < @tplus").dropna(how='all')
#dfs=df.query("visible_from_SOLO==True and STIX_AIA_timedelta_abs < @tplus and r_less_than_50_arcsec").dropna(how='all')
dfgc=dfs.where(dfs.fl_goescls !='').dropna(how='all')
dfgc['GOES_flux']=[goes_class_to_flux(c) for c in dfgc.fl_goescls]
 
 

Flare property distributions¶

Total Flares: 3423¶

Jointly Visible: 1498¶

Jointly Visible, with GOES class: 1165¶

 

Flare Locations¶

 

Counts- flux relationship¶

stix_goes_fit.py

fit=STIX_GOES_fit(dfgc)
fit.do_fit(dfgc)
fit.bin_residuals(dfgc) #gives statistics-based boundaries on predictions - very important!
fit.__dict__
{'start_date': Timestamp('2021-04-24 14:54:07.893000'),
 'end_date': Timestamp('2022-02-02 16:35:08.374000'),
 'nflares': 1165,
 'timestamp': datetime.datetime(2022, 2, 4, 10, 28, 36, 299416),
 'min_counts': 275.8,
 'bins': [2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0],
 'slope': 0.6474373955427877,
 'intercept': -8.017059728369057,
 'rvalue': 0.8404334190665731,
 'pvalue': 1.0143336224873e-311,
 'bin_2.0_sigma': 0.24294403493347283,
 'bin_2.5_sigma': 0.28223799401465166,
 'bin_3.0_sigma': 0.17811255923183106,
 'bin_3.5_sigma': 0.2518162616056952,
 'bin_4.0_sigma': 0.14362285052090484,
 'bin_4.5_sigma': 0.2454768948923288,
 'bin_5.0_sigma': 0.4029749010665078}

Linear fit to log-log data (least squares)¶

$\displaystyle GOES\_flux = 0.647\ log_{10}(peak\_counts)+-8.017$
$\displaystyle R:0.840, p:1.014e-311$

Residuals¶

Observed GOES class - GOES class calculated using linear relationship

log10 (counts) 1 $\sigma$ N flares
< 3 0.272 830
[3,4) 0.205 264
> 4 0.222 71
 
 

Flares not jointly visible - calculate GOES flux¶