def trace_to_surface(seeds, synoptic_map, rss=rss, nrho=60): """ seeds : astropy.coordinates.SkyCoord Seed coordinates on the source surface. synoptic_map : sunpy.map.GenericMap Input synoptic magnetogram. rss : scalar Source surface radius. nrho : int Number of grid points in the radial direciton of the PFSS model. Returns ------- flines : pfsspy.flines.field_lines Traced field lines. pfss_input : pfsspy.Input PFSS input. pfss_output : pfsspy.Output PFSS output. """ pfss_input = pfsspy.Input(synoptic_map, nrho, rss) print('Computing PFSS...') pfss_output = pfsspy.pfss(pfss_input) tracer = pfsspy.tracing.FortranTracer(max_steps=2000) print('Tracing field lines...') flines = tracer.trace(seeds, pfss_output) return flines, pfss_input, pfss_output
def __init__(self, nrho, rss, gong_map=sunpy.map.Map(get_gong_map())): self._nrho = nrho self._rss = rss self.gong_map = gong_map self.input = pfsspy.Input(self.gong_map, self.nrho, self.rss) self._output = pfsspy.pfss(self.input) self.tracer = tracing.PythonTracer()
def dipole_result(dipole_map): nr = 10 rss = 2.5 input = pfsspy.Input(dipole_map, nr, rss) output = pfsspy.pfss(input) return input, output
def test_sunpy_map_input(zero_map): zero_in, _ = zero_map # Check that loading an input map works header = {'cunit1': 'degree', 'cunit2': 'degree'} map = sunpy.map.Map((zero_in.br, header)) input = pfsspy.Input(map, zero_in.grid.nr, zero_in.grid.rss) assert (input.br == zero_in.br).all()
def adapt2pfsspy(dt, rss=2.5, nr=60, adapt_source="GONG", realization="mean", ret_magnetogram=False, data_dir=DefaultPath): #Input : dt (datetime object), rss (source surface radius in Rs) YYYYMMDD = f'{dt.year}{dt.month:02d}{dt.day:02d}' A = {"GONG": 3, "HMI": 4}.get(adapt_source) filepath = sorted(glob.glob(f"{data_dir}/adapt40{A}*{YYYYMMDD}*.fts"))[0] stdout.write(f"Read in {filepath}\r") adapt_map = fits.open(filepath) if realization == "mean": br_adapt_ = np.mean(adapt_map[0].data, axis=0) elif isinstance(realization, int): br_adapt_ = adapt_map[0].data[realization, :, :] else: raise ValueError("realization should either be 'mean' or type int ") # Interpolate to Strumfric Grid (const lat spacing -> const cos(lat) spacing) lat_dr = np.linspace(-90, 90, br_adapt_.shape[0]) lon_dr = np.linspace(0, 360, br_adapt_.shape[1]) clat_dr = np.sin(np.radians(lat_dr)) clat_dr_interp = np.linspace(clat_dr[0], clat_dr[-1], len(clat_dr)) br_adapt = np.zeros(br_adapt_.shape) for ind, _ in enumerate(lon_dr): interper = interp1d(clat_dr, br_adapt_[:, ind]) br_adapt[:, ind] = interper(clat_dr_interp) br_adapt -= np.mean(br_adapt) br_adapt *= 1e5 # G -> nT if ret_magnetogram: return br_adapt peri_input = pfsspy.Input(br_adapt, nr, rss, dtime=dt) peri_output = pfsspy.pfss(peri_input) return peri_output
def compute_pfss(gong_fname, dtime): gong_map = sunpy.map.Map(gong_fname) br = gong_map.data header = gong_map.meta br = br - np.mean(br) br = np.roll(br, header['CRVAL1'] + 180, axis=1) header['CRVAL1'] = 180 header['DATE_ORI'] = header['DATE'] header['date-obs'] = Time(dtime).isot gong_map = sunpy.map.Map(br, header) nrho = 60 rss = 2.5 input = pfsspy.Input(gong_map, nrho, rss) ssfile = gong_fname.with_suffix('.ssmap') if ssfile.exists(): ssmap = np.loadtxt(ssfile) ssmap = sunpy.map.Map(ssmap, header) else: print('Calculating PFSS solution') # Compute PFSS solution and source surface map output = pfsspy.pfss(input) ssdata = output.source_surface_br.data np.savetxt(ssfile, ssdata) ssmap = output.source_surface_br return input, ssmap
def zero_map(): # Test a completely zero input ns = 30 nphi = 20 nr = 10 rss = 2.5 br = np.zeros((ns, nphi)) input = pfsspy.Input(br, nr, rss) output = pfsspy.pfss(input) return input, output
def zero_map(): # Test a completely zero input ns = 30 nphi = 20 nr = 10 rss = 2.5 br = np.zeros((nphi, ns)) header = pfsspy.utils.carr_cea_wcs_header(Time('1992-12-21'), br.shape) input_map = Map((br.T, header)) input = pfsspy.Input(input_map, nr, rss) output = pfsspy.pfss(input) return input, output
def test_bunit(gong_map): # Regression test to check that the output of pfss doesn't change m = sunpy.map.Map(gong_map) pfss_in = pfsspy.Input(m, 2, 2) pfss_out = pfsspy.pfss(pfss_in) assert pfss_out.bunit == u.G pfss_out.input_map.meta['bunit'] = 'notaunit' with pytest.warns(UserWarning, match='Could not parse unit string "notaunit"'): assert pfss_out.bunit is None pfss_out.input_map.meta.pop('bunit') assert pfss_out.bunit is None
def test_pfss(gong_map): # Regression test to check that the output of pfss doesn't change m = sunpy.map.Map(gong_map) # Resample to lower res for easier comparisons m = m.resample([30, 15] * u.pix) m = sunpy.map.Map(m.data - np.mean(m.data), m.meta) expected = np.loadtxt(test_data / 'br_in.txt') np.testing.assert_equal(m.data, expected) pfss_in = pfsspy.Input(m, 50, 2) pfss_out = pfsspy.pfss(pfss_in) br = pfss_out.source_surface_br.data expected = np.loadtxt(test_data / 'br_out.txt') # atol is emperically set for tests to pass on CI np.testing.assert_allclose(br, expected, atol=1e-13, rtol=0)
def __init__(self, gongMap, SSradius: float = 2.5, nrho: int = 25, tracer=tracing.FortranTracer()): """ PFSS solution to solar magnetic field is calculated on a 3D grid (phi, s, rho). rho = ln(r), and r is the standard spherical radial coordinate. """ self.gongMap = gongMap self.SSradius = SSradius self.SSradiusRsun = SSradius * const.R_sun self.nrho = nrho self.input = pfsspy.Input(gongMap, nrho, SSradius) self.output = pfsspy.pfss(self.input) self.tracer = tracer
def dipole_map(): # Test a completely zero input ntheta = 30 nphi = 20 nr = 10 rss = 2.5 phi = np.linspace(0, 2 * np.pi, nphi) theta = np.linspace(-np.pi / 2, np.pi / 2, ntheta) theta, phi = np.meshgrid(theta, phi) def dipole_Br(r, theta): return 2 * np.sin(theta) / r**3 br = dipole_Br(1, theta).T input = pfsspy.Input(br, nr, rss) output = pfsspy.pfss(input) return input, output
def gong2pfsspy(dt, rss=2.5, nr=60, ret_magnetogram=False, data_dir=DefaultPath): #Input : dt (datetime object), rss (source surface radius in Rs) YYYYMMDD = f'{dt.year}{dt.month:02d}{dt.day:02d}' gongpath = f'mrzqs{YYYYMMDD}/' filepath = sorted(glob.glob(f'{data_dir}/mrzqs{YYYYMMDD[2:]}*.fits'))[0] stdout.write(f"Read in {filepath}\r") # Sunpy 1.03 error : need to fix header of gong maps to include units am = fits.open(filepath)[0] am.header.append(('CUNIT1', 'degree')) am.header.append(('CUNIT2', 'degree')) gong_map = sunpy.map.Map(am.data, am.header) br_gong = extract_br(gong_map) if ret_magnetogram: return br_gong peri_input = pfsspy.Input(br_gong, nr, rss, dtime=dt) peri_output = pfsspy.pfss(peri_input) return peri_output
def hmi2pfsspy(dt, rss=2.5, nr=60, ret_magnetogram=False, data_dir=DefaultPath): #Input : dt (datetime object), rss (source surface radius in Rs) YYYYMMDD = f'{dt.year}{dt.month:02d}{dt.day:02d}' filepath = glob.glob( f'{data_dir}/hmi.mrdailysynframe_small_720s.{YYYYMMDD}*.fits')[0] stdout.write(f"Read in {filepath}\r") hmi_fits = fits.open(filepath)[0] hmi_fits.header['CUNIT2'] = 'degree' for card in ['HGLN_OBS', 'CRDER1', 'CRDER2', 'CSYSER1', 'CSYSER2']: hmi_fits.header[card] = 0 hmi_map = sunpy.map.Map(hmi_fits.data, hmi_fits.header) hmi_map.meta['CRVAL1'] = 120 + sun.L0(time=hmi_map.meta['T_OBS']).value br_hmi = extract_br(hmi_map) if ret_magnetogram: return br_hmi peri_input = pfsspy.Input(br_hmi, nr, rss, dtime=dt) peri_output = pfsspy.pfss(peri_input) return peri_output
def derosa2pfsspy(dt, rss=2.5, nr=60, ret_magnetogram=False, data_dir=DefaultPath): #Input : dt (datetime object), rss (source surface radius in Rs) YYYYMMDD = f'{dt.year}{dt.month:02d}{dt.day:02d}' dr_times = ['_000400.h5', '_060432.h5', '_120400.h5', '_180328.h5'] dr_time = dr_times[np.argmin(np.abs(dt.hour - np.array([0, 6, 12, 18])))] filepath = f'{data_dir}/Bfield_{YYYYMMDD}{dr_time}' f = h5py.File(filepath, 'r') stdout.write(f"Read in {filepath}\r") fdata = f['ssw_pfss_extrapolation'] br_dr = fdata['BR'][0, :, :, :] * 1e5 nr_dr = fdata['NR'] nlat_dr = fdata['NLAT'] nlon_dr = fdata['NLON'] r_dr = fdata['RIX'][0] lat_dr = fdata['LAT'][0] c_dr = np.cos(np.radians(90 - lat_dr)) lon_dr = fdata['LON'][0] date = fdata['MODEL_DATE'] magnetogram = br_dr[0, :, :] # Interpolate to Strumfric Grid (const lat spacing -> const cos(lat) spacing) clat_dr = np.sin(np.radians(lat_dr)) clat_dr_interp = np.linspace(clat_dr[0], clat_dr[-1], len(clat_dr)) br_dr_interp = np.zeros(magnetogram.shape) for ind, _ in enumerate(lon_dr): interper = interp1d(clat_dr, magnetogram[:, ind]) br_dr_interp[:, ind] = interper(clat_dr_interp) br_dr_interp -= np.mean( br_dr_interp) # Remove Mean offest for pfsspy FFT based PFSS extrap if ret_magnetogram: return (magnetogram, br_dr_interp) peri_input = pfsspy.Input(br_dr_interp, nr, rss, dtime=dt) peri_output = pfsspy.pfss(peri_input) return peri_output
def test_monopole_warning(dipole_map): dipole_map = sunpy.map.Map(dipole_map.data + 1, dipole_map.meta) with pytest.warns(UserWarning, match='Input data has a non-zero mean'): pfsspy.Input(dipole_map, 20, 2)
# so roll to get it at 0deg. This way the input magnetic field is in a # Carrington frame of reference, which matters later when lining the field # lines up with the AIA image. br = np.roll(br, header['CRVAL1'] + 180, axis=1) ############################################################################### # The PFSS solution is calculated on a regular 3D grid in (phi, s, rho), where # rho = ln(r), and r is the standard spherical radial coordinate. We need to # define the number of grid points in rho, and the source surface radius. nrho = 60 rss = 2.5 ############################################################################### # From the boundary condition, number of radial grid points, and source # surface, we now construct an `Input` object that stores this information input = pfsspy.Input(br, nrho, rss, dtime=dtime) ############################################################################### # Using the `Input` object, plot the input photospheric magnetic field fig, ax = plt.subplots() mesh = input.plot_input(ax) fig.colorbar(mesh) ax.set_title('Input field') ############################################################################### # We can also plot the AIA map to give an idea of the global picture. There # is a nice active region in the top right of the AIA plot, that can also # be seen in the top left of the photospheric field plot above. ax = plt.subplot(1, 1, 1, projection=aia) aia.plot(ax)
br = dipole_Br(1, s).T ############################################################################### # The PFSS solution is calculated on a regular 3D grid in (phi, s, rho), where # rho = ln(r), and r is the standard spherical radial coordinate. We need to # define the number of rho grid points, and the source surface radius. nrho = 50 rss = 2.5 ############################################################################### # From the boundary condition, number of radial grid points, and source # surface, we now construct an Input object that stores this information input = pfsspy.Input(br, nrho, rss) ############################################################################### # Using the Input object, plot the input field m = input.map fig = plt.figure() ax = plt.subplot(projection=m) m.plot() plt.colorbar() ax.set_title('Input dipole field') ############################################################################### # Now calculate the PFSS solution. output = pfsspy.pfss(input) ###############################################################################
def test_sunpy_map_input(zero_map): zero_in, _ = zero_map # Check that loading an input map works map = sunpy.map.Map((zero_in.br, {})) input = pfsspy.Input(map, zero_in.grid.nr, zero_in.grid.rss) assert (input.br == zero_in.br).all()
def test_non_map_input(): with pytest.raises(ValueError, match='br must be a SunPy Map'): pfsspy.Input(np.random.rand(2, 2), 1, 1)
def test_nan_value(dipole_map): dipole_map.data[0, 0] = np.nan with pytest.raises(ValueError, match='At least one value in the input is NaN'): pfsspy.Input(dipole_map, 5, 2.5)
def test_wrong_projection_error(dipole_map): dipole_map.meta['ctype1'] = 'HGLN-CAR' with pytest.raises(ValueError, match='must be CEA'): pfsspy.Input(dipole_map, 5, 2.5)
br = dipole_Br(1, s) ############################################################################### # The PFSS solution is calculated on a regular 3D grid in (phi, s, rho), where # rho = ln(r), and r is the standard spherical radial coordinate. We need to # define the number of rho grid points, and the source surface radius. nrho = 30 rss = 2.5 ############################################################################### # From the boundary condition, number of radial grid points, and source # surface, we now construct an Input object that stores this information header = pfsspy.utils.carr_cea_wcs_header(Time('2020-1-1'), br.shape) input_map = sunpy.map.Map((br.T, header)) input = pfsspy.Input(input_map, nrho, rss) ############################################################################### # Using the Input object, plot the input field m = input.map fig = plt.figure() ax = plt.subplot(projection=m) m.plot() plt.colorbar() ax.set_title('Input dipole field') ############################################################################### # Now calculate the PFSS solution. output = pfsspy.pfss(input) ###############################################################################
# Crop to the desired active region aia_submap = aia.submap( SkyCoord(Tx=300 * u.arcsec, Ty=100 * u.arcsec, frame=aia.coordinate_frame), SkyCoord(Tx=650 * u.arcsec, Ty=450 * u.arcsec, frame=aia.coordinate_frame), ) ############################### # Field Extrapolation # ############################### # Define the number of grid points in rho nrho = 100 # And the source surface radius. rss = 2.5 # Construct an `Input` object that stores this information pfss_input = pfsspy.Input(br, nrho, rss, dtime=aia_submap.date) # Compute the PFSS solution from the GONG magnetic field input pfss_output = pfsspy.pfss(pfss_input) ############################### # Field Line Tracing # ############################### center_hgc = aia_submap.center.transform_to('heliographic_carrington') # Construct a grid of seed points to trace some magnetic field lines # These seed points are grouped about the center of the AR s, phi = np.meshgrid( np.linspace( np.sin(center_hgc.lat) - 0.07, np.sin(center_hgc.lat) + 0.03, 15), np.deg2rad(
br = br - np.nanmean(br) offset = 5 # GONG maps have their LH edge at -180deg, so roll to get it at 0deg br = np.roll(br, header['CRVAL1'] + 180 + offset, axis=1) # Set up PFSS input # --- # In[8]: import pfsspy nr = 60 # Number of radial model points rss = 2.5 # Source surface radius # Create PFSS input object input = pfsspy.Input(br, nr, rss, dtime=aia.date) # Plot input magnetic field import matplotlib.colors as mcolor fig, ax = plt.subplots() mesh = input.plot_input(ax, norm=mcolor.SymLogNorm(5)) fig.colorbar(mesh) ax.set_title('Input GONG map') # Calculate PFSS solution # --- # In[9]: output = pfsspy.pfss(input)
# at your own risk! gong_map = sunpy.map.Map(gong_fname) # Remove the mean gong_map = sunpy.map.Map(gong_map.data - np.mean(gong_map.data), gong_map.meta) ############################################################################### # The PFSS solution is calculated on a regular 3D grid in (phi, s, rho), where # rho = ln(r), and r is the standard spherical radial coordinate. We need to # define the number of rho grid points, and the source surface radius. nrho = 35 rss = 2.5 ############################################################################### # From the boundary condition, number of radial grid points, and source # surface, we now construct an Input object that stores this information input = pfsspy.Input(gong_map, nrho, rss) def set_axes_lims(ax): ax.set_xlim(0, 360) ax.set_ylim(0, 180) ############################################################################### # Using the Input object, plot the input field m = input.map fig = plt.figure() ax = plt.subplot(projection=m) m.plot() plt.colorbar() ax.set_title('Input field')
nr = 50 rss = 2.5 phi = np.linspace(0, 2 * np.pi, nphi) theta = np.linspace(-np.pi / 2, np.pi / 2, ntheta) theta, phi = np.meshgrid(theta, phi) def dipole_Br(r, theta): return 2 * np.sin(theta) / r**3 br = dipole_Br(1, theta) br = sunpy.map.Map(br.T, pfsspy.utils.carr_cea_wcs_header('2010-01-01', br.shape)) pfss_input = pfsspy.Input(br, nr, rss) pfss_output = pfsspy.pfss(pfss_input) print('Computed PFSS solution') ############################################################################### # Trace some field lines seed0 = np.atleast_2d(np.array([1, 1, 0])) tracers = [pfsspy.tracing.PythonTracer(), pfsspy.tracing.FortranTracer()] nseeds = 2**np.arange(14) times = [[], []] for nseed in nseeds: print(nseed) seeds = np.repeat(seed0, nseed, axis=0) r, lat, lon = pfsspy.coords.cart2sph(seeds[:, 0], seeds[:, 1], seeds[:, 2]) r = r * astropy.constants.R_sun
# to the FITS standard, so we need to fix them first. hmi_map = sunpy.map.Map(files[0]) pfsspy.utils.fix_hmi_meta(hmi_map) print('Data shape: ', hmi_map.data.shape) ############################################################################### # Since this map is far to big to calculate a PFSS solution quickly, lets # resample it down to a smaller size. hmi_map = hmi_map.resample([360, 180] * u.pix) print('New shape: ', hmi_map.data.shape) ############################################################################### # Now calculate the PFSS solution nrho = 35 rss = 2.5 input = pfsspy.Input(hmi_map, nrho, rss) output = pfsspy.pfss(input) ############################################################################### # Using the Output object we can plot the source surface field, and the # polarity inversion line. ss_br = output.source_surface_br # Create the figure and axes fig = plt.figure() ax = plt.subplot(projection=ss_br) # Plot the source surface map ss_br.plot() # Plot the polarity inversion line ax.plot_coord(output.source_surface_pils[0]) # Plot formatting