diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..5c6435e --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,22 @@ +1.0.0 (2024-05-31) +================== + +This is the first release and aims to keep the API the same from `pfsspy` to +`sunkit_magex`. The main difference is that you will need to replace the +import like so: + + .. code-block:: python + + # Before + import pfsspy + from pfsspy import tracing + + # After + from sunkit_magex import pfss as pfsspy + from sunkit_magex.pfss import tracing + +The main changes from the previous release of `pfsspy` are as follows: + +* The ``GongSynopticMap`` class has moved into `sunpy`, note that the new + class in ``sunpy`` is slightly different in that it does not modify the + header. diff --git a/changelog/63.breaking.1.rst b/changelog/63.breaking.1.rst new file mode 100644 index 0000000..d5013c1 --- /dev/null +++ b/changelog/63.breaking.1.rst @@ -0,0 +1 @@ +Made streamtracer a hard dependency of sunkit-magex. diff --git a/changelog/63.breaking.2.rst b/changelog/63.breaking.2.rst new file mode 100644 index 0000000..a13183f --- /dev/null +++ b/changelog/63.breaking.2.rst @@ -0,0 +1,4 @@ +Removed the following pfss utils: + +- pfss.utils.fix_hmi_meta +- pfss.utils.load_adapt diff --git a/changelog/63.breaking.rst b/changelog/63.breaking.rst new file mode 100644 index 0000000..28b0e40 --- /dev/null +++ b/changelog/63.breaking.rst @@ -0,0 +1 @@ +Increased the minimum required version of ``sunpy`` to v6.0.0. diff --git a/changelog/README.rst b/changelog/README.rst new file mode 100644 index 0000000..7d388e3 --- /dev/null +++ b/changelog/README.rst @@ -0,0 +1,34 @@ +========= +Changelog +========= + +.. note:: + + This README was adapted from the pytest changelog readme under the terms of the MIT licence. + +This directory contains "news fragments" which are short files that contain a small **ReST**-formatted text that will be added to the next ``CHANGELOG``. + +The ``CHANGELOG`` will be read by users, so this description should be aimed at SunPy users instead of describing internal changes which are only relevant to the developers. + +Make sure to use full sentences with correct case and punctuation, for example:: + + Add support for Helioprojective coordinates in `sunpy.coordinates.frames`. + +Please try to use Sphinx intersphinx using backticks. + +Each file should be named like ``.[.].rst``, where ```` is a pull request number, ``COUNTER`` is an optional number if a PR needs multiple entries with the same type and ```` is one of: + +* ``breaking``: A change which requires users to change code and is not backwards compatible. (Not to be used for removal of deprecated features.) +* ``feature``: New user facing features and any new behavior. +* ``bugfix``: Fixes a reported bug. +* ``doc``: Documentation addition or improvement, like rewording an entire session or adding missing docs. +* ``deprecation``: Feature deprecation +* ``removal``: Feature removal. +* ``trivial``: A change which has no user facing effect or is tiny change. + +So for example: ``123.feature.rst``, ``456.bugfix.rst``. + +If you are unsure what pull request type to use, don't hesitate to ask in your PR. + +Note that the ``towncrier`` tool will automatically reflow your text, so it will work best if you stick to a single paragraph, but multiple sentences and links are OK and encouraged. +You can install ``towncrier`` and then run ``towncrier --draft`` if you want to get a preview of how your change will look in the final release notes. diff --git a/docs/changelog.rst b/docs/changelog.rst index c22f33b..c9c3609 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,25 +4,7 @@ Full Changelog ************** -1.0.0 (2024-05-31) -================== - -This is the first release and aims to keep the API the same from `pfsspy` to -`sunkit_magex`. The main difference is that you will need to replace the -import like so: - - .. code-block:: python - - # Before - import pfsspy - from pfsspy import tracing - - # After - from sunkit_magex import pfss as pfsspy - from sunkit_magex.pfss import tracing - -The main changes from the previous release of `pfsspy` are as follows: - -* The ``GongSynopticMap`` class has moved into `sunpy`, note that the new - class in ``sunpy`` is slightly different in that it does not modify the - header. +.. changelog:: + :towncrier: ../ + :towncrier-skip-if-empty: + :changelog_file: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py index a82bbe1..9cae120 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,6 +35,7 @@ "sphinx_automodapi.automodapi", "sphinx_automodapi.smart_resolver", 'sphinx_gallery.gen_gallery', + 'sphinx_changelog', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/installing.rst b/docs/installing.rst index 9958e7f..e2c4350 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -17,7 +17,7 @@ Alternatively ``sunkit-magex`` can be installed from PyPI using: pip install sunkit-magex This will install sunkit_magex and all of its dependencies. -In addition to the core dependencies, there are optional dependencies (numba, streamtracer) that can improve code performance. +In addition to the core dependencies, there are optional dependencies (numba) that can improve code performance. These can be installed with .. code:: shell diff --git a/docs/performance.rst b/docs/performance.rst index b7d282a..3c90dbe 100644 --- a/docs/performance.rst +++ b/docs/performance.rst @@ -11,9 +11,8 @@ To enable this simply `install numba`_ and use `sunkit_magex.pfss` as normal. Streamline tracing ================== -`sunkit_magex.pfss` has two streamline tracers: a pure python `sunkit_magex.pfss.tracing.PythonTracer` and a complied tracer `sunkit_magex.pfss.tracing.FortranTracer`. -The compiled version is significantly faster, using the `streamtracer`_ package. +`sunkit_magex.pfss` uses a complied tracer `sunkit_magex.pfss.tracing.PerformanceTracer` using the `streamtracer`_ package. .. _numba: https://numba.pydata.org .. _install numba: http://numba.pydata.org/numba-doc/latest/user/installing.html -.. _streamtracer: https://github.com/sunpy/streamtracer +.. _streamtracer: https://docs.sunpy.org/projects/streamtracer/en/stable/ diff --git a/examples/internals/tracer_performance.py b/examples/internals/tracer_performance.py index 057b3b0..b69bd25 100644 --- a/examples/internals/tracer_performance.py +++ b/examples/internals/tracer_performance.py @@ -42,8 +42,7 @@ def dipole_Br(r, theta): # Trace some field lines. seed0 = np.atleast_2d(np.array([1, 1, 0])) -tracers = [pfss.tracing.PythonTracer(), - pfss.tracing.FortranTracer()] +tracers = [pfss.tracing.PerformanceTracer()] nseeds = 2**np.arange(14) times = [[], []] diff --git a/examples/testing/plot_error_map.py b/examples/testing/plot_error_map.py index 06d8531..16db531 100644 --- a/examples/testing/plot_error_map.py +++ b/examples/testing/plot_error_map.py @@ -48,7 +48,7 @@ dthetas = [] print(f'Tracing {step_size}...') # Trace -tracer = pfss.tracing.FortranTracer(step_size=step_size) +tracer = pfss.tracing.PerformanceTracer(step_size=step_size) flines = tracer.trace(seeds, pfss_out) # Set a mask of open field lines mask = flines.connectivities.astype(bool).reshape(theta.shape) diff --git a/examples/testing/tracer_step_size.py b/examples/testing/tracer_step_size.py index b641b3c..6237a50 100644 --- a/examples/testing/tracer_step_size.py +++ b/examples/testing/tracer_step_size.py @@ -45,7 +45,7 @@ for step_size in step_sizes: print(f'Tracing {step_size}...') # Trace - tracer = tracing.FortranTracer(step_size=step_size) + tracer = tracing.PerformanceTracer(step_size=step_size) flines = tracer.trace(seeds, pfss_out) # Set a mask of open field lines mask = flines.connectivities.astype(bool).reshape(theta.shape) diff --git a/examples/using_sunkit_magex_pfss/plot_aia_overplotting.py b/examples/using_sunkit_magex_pfss/plot_aia_overplotting.py index 164542b..ad6bb17 100644 --- a/examples/using_sunkit_magex_pfss/plot_aia_overplotting.py +++ b/examples/using_sunkit_magex_pfss/plot_aia_overplotting.py @@ -112,7 +112,7 @@ ############################################################################### # Trace field lines from the footpoints defined above. -tracer = tracing.FortranTracer() +tracer = tracing.PerformanceTracer() flines = tracer.trace(seeds, pfss_out) ############################################################################### diff --git a/examples/using_sunkit_magex_pfss/plot_dipole.py b/examples/using_sunkit_magex_pfss/plot_dipole.py index 5714963..4683c71 100644 --- a/examples/using_sunkit_magex_pfss/plot_dipole.py +++ b/examples/using_sunkit_magex_pfss/plot_dipole.py @@ -102,7 +102,7 @@ def dipole_Br(r, s): lat = np.linspace(-np.pi / 2, np.pi / 2, 33) * u.rad seeds = SkyCoord(lon, lat, r, frame=pfss_out.coordinate_frame) -tracer = pfss.tracing.FortranTracer() +tracer = pfss.tracing.PerformanceTracer() field_lines = tracer.trace(seeds, pfss_out) for field_line in field_lines: diff --git a/examples/using_sunkit_magex_pfss/plot_field_line_magnetic_field.py b/examples/using_sunkit_magex_pfss/plot_field_line_magnetic_field.py index 7c18b6a..682cd4c 100644 --- a/examples/using_sunkit_magex_pfss/plot_field_line_magnetic_field.py +++ b/examples/using_sunkit_magex_pfss/plot_field_line_magnetic_field.py @@ -40,7 +40,7 @@ # Now take a seed point, and trace a magnetic field line through the PFSS # solution from this point -tracer = pfss.tracing.FortranTracer() +tracer = pfss.tracing.PerformanceTracer() r = 1.2 * const.R_sun lat = 70 * u.deg lon = 0 * u.deg diff --git a/examples/using_sunkit_magex_pfss/plot_gong.py b/examples/using_sunkit_magex_pfss/plot_gong.py index 3a0ef1f..a006112 100644 --- a/examples/using_sunkit_magex_pfss/plot_gong.py +++ b/examples/using_sunkit_magex_pfss/plot_gong.py @@ -108,7 +108,7 @@ def set_axes_lims(ax): fig = plt.figure() ax = fig.add_subplot(111, projection='3d') -tracer = pfss.tracing.FortranTracer() +tracer = pfss.tracing.PerformanceTracer() r = 1.2 * const.R_sun lat = np.linspace(-np.pi / 2, np.pi / 2, 8, endpoint=False) lon = np.linspace(0, 2 * np.pi, 8, endpoint=False) diff --git a/examples/using_sunkit_magex_pfss/plot_open_closed_map.py b/examples/using_sunkit_magex_pfss/plot_open_closed_map.py index a5c41bd..0836da5 100644 --- a/examples/using_sunkit_magex_pfss/plot_open_closed_map.py +++ b/examples/using_sunkit_magex_pfss/plot_open_closed_map.py @@ -54,7 +54,7 @@ # Trace the field lines. print('Tracing field lines...') -tracer = pfss.tracing.FortranTracer(max_steps=2000) +tracer = pfss.tracing.PerformanceTracer(max_steps=2000) field_lines = tracer.trace(seeds, pfss_out) print('Finished tracing field lines') diff --git a/examples/utils/plot_car_reproject.py b/examples/utils/plot_car_reproject.py index d7139e9..06e0ad7 100644 --- a/examples/utils/plot_car_reproject.py +++ b/examples/utils/plot_car_reproject.py @@ -12,13 +12,14 @@ """ import matplotlib.pyplot as plt +import sunpy.map + from sunkit_magex import pfss ############################################################################### # Load a sample ADAPT map, which has a CAR projection. -adapt_maps = pfss.utils.load_adapt(pfss.sample_data.get_adapt_map()) -adapt_map_car = adapt_maps[0] +adapt_map_car = sunpy.map.Map(pfss.sample_data.get_adapt_map(), hdu=0) ############################################################################### # Re-project into a CEA projection. diff --git a/pyproject.toml b/pyproject.toml index aa4af83..9bb3669 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dynamic = ["version"] [project.optional-dependencies] tests = [ + "sunkit-magex[all]", "pytest", "pytest-doctestplus", "pytest-cov", @@ -35,16 +36,15 @@ tests = [ "pytest-arraydiff", "parfive", "reproject", - "sympy", ] docs = [ - "sphinx", - "sphinx-automodapi", + "sunkit-magex[all]", "pillow", "reproject", + "sphinx-automodapi", + "sphinx-changelog", "sphinx-gallery", - "sunpy[net,map]", - "sympy", + "sphinx", "sunpy-sphinx-theme", ] performance = [ @@ -60,7 +60,7 @@ dev = ["sunkit-magex[all,tests,docs]"] Homepage = "https://sunpy.org" Download = "https://pypi.org/project/sunkit-magex/" "Source Code" = "https://github.com/sunpy/sunkit-magex/" -Documentation = "https://docs.sunpy.org/" +Documentation = "https://docs.sunpy.org/projects/sunkit-magex/en/stable/" Changelog = "https://docs.sunpy.org/en/stable/whatsnew/changelog.html" "Issue Tracker" = "https://github.com/sunpy/sunkit-magex/issues" @@ -73,3 +73,47 @@ exclude = ["sunkit_magex._dev*"] [tool.setuptools_scm] write_to = "sunkit_magex/_version.py" + +# TODO: This should be in towncrier.toml but Giles currently only works looks in +# pyproject.toml we should move this back when it's fixed. +[tool.towncrier] + package = "sunkit_magex" + filename = "CHANGELOG.rst" + directory = "changelog/" + issue_format = "`#{issue} `__" + title_format = "{version} ({project_date})" + + [[tool.towncrier.type]] + directory = "breaking" + name = "Breaking Changes" + showcontent = true + + [[tool.towncrier.type]] + directory = "deprecation" + name = "Deprecations" + showcontent = true + + [[tool.towncrier.type]] + directory = "removal" + name = "Removals" + showcontent = true + + [[tool.towncrier.type]] + directory = "feature" + name = "New Features" + showcontent = true + + [[tool.towncrier.type]] + directory = "bugfix" + name = "Bug Fixes" + showcontent = true + + [[tool.towncrier.type]] + directory = "doc" + name = "Documentation" + showcontent = true + + [[tool.towncrier.type]] + directory = "trivial" + name = "Internal Changes" + showcontent = true diff --git a/sunkit_magex/pfss/tests/conftest.py b/sunkit_magex/pfss/tests/conftest.py index 3ff7915..8581044 100644 --- a/sunkit_magex/pfss/tests/conftest.py +++ b/sunkit_magex/pfss/tests/conftest.py @@ -6,7 +6,6 @@ from sunpy.map import Map import sunkit_magex.pfss -from sunkit_magex.pfss import utils from sunkit_magex.tests.helpers import get_dummy_map_from_header, get_fitsfile_from_header @@ -68,7 +67,7 @@ def adapt_test_file(tmp_path): @pytest.fixture def adapt_map(adapt_test_file): - return utils.load_adapt(adapt_test_file) + return Map(adapt_test_file, hdu=0) @pytest.fixture diff --git a/sunkit_magex/pfss/tests/test_pfss.py b/sunkit_magex/pfss/tests/test_pfss.py index c06252e..21f75ae 100644 --- a/sunkit_magex/pfss/tests/test_pfss.py +++ b/sunkit_magex/pfss/tests/test_pfss.py @@ -58,7 +58,7 @@ def test_expansion_factor(dipole_result): inp, out = dipole_result out_frame = out.coordinate_frame - tracer = tracing.PythonTracer() + tracer = tracing.PerformanceTracer() seed = coord.SkyCoord(0 * u.deg, 80 * u.deg, 1.1 * R_sun, frame=out_frame) field_line = tracer.trace(seed, out)[0] assert field_line.expansion_factor > 1 @@ -83,7 +83,7 @@ def test_field_line_polarity(dipole_result): input, out = dipole_result out_frame = out.coordinate_frame - tracer = tracing.PythonTracer() + tracer = tracing.PerformanceTracer() seed = coord.SkyCoord(0 * u.deg, 90*u.deg, 1.01 * R_sun, frame=out_frame) field_line = tracer.trace(seed, out) assert field_line[0].polarity == 1 @@ -102,7 +102,7 @@ def test_footpoints(dipole_result): input, out = dipole_result out_frame = out.coordinate_frame - tracer = tracing.PythonTracer(atol=1e-8, rtol=1e-8) + tracer = tracing.PerformanceTracer(atol=1e-8, rtol=1e-8) def check_radius(coord, r): coord.representation_type = 'spherical' diff --git a/sunkit_magex/pfss/tests/test_tracers.py b/sunkit_magex/pfss/tests/test_tracers.py index d617aea..15d940b 100644 --- a/sunkit_magex/pfss/tests/test_tracers.py +++ b/sunkit_magex/pfss/tests/test_tracers.py @@ -9,9 +9,8 @@ from sunkit_magex.pfss import tracing -@pytest.fixture(params=[tracing.PythonTracer(), - tracing.FortranTracer()], - ids=['python', 'compiled']) +@pytest.fixture(params=[tracing.PerformanceTracer()], + ids=['compiled']) def flines(dipole_result, request): tracer = request.param _, out = dipole_result @@ -43,13 +42,13 @@ def test_fline_step_size(dipole_result): seed = coord.SkyCoord(2*u.deg, -45*u.deg, 1.01*const.R_sun, frame=out_frame) - tracer = tracing.FortranTracer(step_size=0.5) + tracer = tracing.PerformanceTracer(step_size=0.5) flines = tracer.trace(seed, out) assert out.grid.nr == 10 # With a step size of 0.5, this should be ~20 assert len(flines[0]) == 21 - tracer = tracing.FortranTracer(step_size=0.2) + tracer = tracing.PerformanceTracer(step_size=0.2) flines = tracer.trace(seed, out) assert out.grid.nr == 10 # With a step size of 0.2, this should be ~50 @@ -57,7 +56,7 @@ def test_fline_step_size(dipole_result): def test_rot_warning(dipole_result): - tracer = tracing.FortranTracer(max_steps=2) + tracer = tracing.PerformanceTracer(max_steps=2) input, out = dipole_result out_frame = out.coordinate_frame seed = coord.SkyCoord(0*u.deg, -45*u.deg, 1.01*const.R_sun, diff --git a/sunkit_magex/pfss/tests/test_utils.py b/sunkit_magex/pfss/tests/test_utils.py index e66b574..7d8c4dd 100644 --- a/sunkit_magex/pfss/tests/test_utils.py +++ b/sunkit_magex/pfss/tests/test_utils.py @@ -7,17 +7,14 @@ import sunpy.map -import sunkit_magex.pfss -from sunkit_magex.pfss import utils - -# Ignore missing metadata warnings -pytestmark = [pytest.mark.filterwarnings('ignore:Missing metadata for observer')] - -def test_load_adapt(adapt_test_file): - adaptMapSequence = utils.load_adapt(adapt_test_file) - assert isinstance(adaptMapSequence, sunpy.map.MapSequence) - for map_ in adaptMapSequence: - assert map_.meta['model'] == "ADAPT" +import sunkit_magex.pfss as pfss +from sunkit_magex.pfss.utils import ( + car_to_cea, + carr_cea_wcs_header, + is_cea_map, + is_full_sun_synoptic_map, + roll_map, +) def test_header_generation(): @@ -25,7 +22,7 @@ def test_header_generation(): nphi = 90 dtime = '2001-01-01 00:00:00' shape = [nphi, ntheta] - header = sunkit_magex.pfss.utils.carr_cea_wcs_header(dtime, shape) + header = pfss.carr_cea_wcs_header(dtime, shape) assert header['LONPOLE'] == 0 assert header['CTYPE1'] == 'CRLN-CEA' assert header['CTYPE2'] == 'CRLT-CEA' @@ -58,20 +55,19 @@ def test_header_generation(): @pytest.mark.parametrize('error', [True, False]) def test_validation(dipole_map, error): - assert utils.is_cea_map(dipole_map, error) - assert utils.is_full_sun_synoptic_map(dipole_map, error) + assert is_cea_map(dipole_map, error) + assert is_full_sun_synoptic_map(dipole_map, error) def test_validation_not_full_map(dipole_map): dipole_map.meta['cdelt1'] = 0.001 - assert not utils.is_full_sun_synoptic_map(dipole_map) + assert not is_full_sun_synoptic_map(dipole_map) with pytest.raises(ValueError, match='Number of points in phi direction times'): - utils.is_full_sun_synoptic_map(dipole_map, error=True) + is_full_sun_synoptic_map(dipole_map, error=True) -def test_car_reproject(adapt_test_file): - adapt_map = utils.load_adapt(adapt_test_file)[0] - adapt_reproj = utils.car_to_cea(adapt_map) +def test_car_reproject(adapt_map): + adapt_reproj = car_to_cea(adapt_map) assert np.all(np.isfinite(adapt_map.data)) assert np.all(np.isfinite(adapt_reproj.data)) @@ -81,13 +77,13 @@ def test_car_reproject(adapt_test_file): assert adapt_reproj.meta[f'CTYPE{i}'][5:8] == 'CEA' with pytest.raises(ValueError, match='method must be one of'): - utils.car_to_cea(adapt_map, method='gibberish') + car_to_cea(adapt_map, method='gibberish') def test_roll_map(adapt_map, gong_map): lh_edge_test = 0.0 * u.deg gong_map = sunpy.map.Map(gong_map) - rolled_map = utils.roll_map(gong_map, + rolled_map = roll_map(gong_map, lh_edge_lon=lh_edge_test) # Test ref pixel rolled correctly @@ -99,38 +95,38 @@ def test_roll_map(adapt_map, gong_map): assert np.all(np.isfinite(rolled_map.data)) # Test output map is full sun synoptic - assert utils.is_full_sun_synoptic_map(rolled_map, error=True) + assert is_full_sun_synoptic_map(rolled_map, error=True) # Test reproject method error handling with pytest.raises(ValueError, match='method must be one of'): - utils.roll_map(gong_map, method='gibberish') + roll_map(gong_map, method='gibberish') # Test left hand edge input type validation # 1. No Units with pytest.raises(TypeError, match="has no 'unit' attribute"): - utils.roll_map(adapt_map, lh_edge_lon=0) + roll_map(adapt_map, lh_edge_lon=0) # 2. Incompatible units with pytest.raises(u.UnitsError, match="must be in units convertible to 'deg'"): - utils.roll_map(adapt_map, lh_edge_lon=0 * u.m) + roll_map(adapt_map, lh_edge_lon=0 * u.m) # Test left hand edge input range validation with pytest.raises(ValueError, match='lh_edge_lon must be in'): - utils.roll_map(adapt_map, lh_edge_lon=361 * u.deg) + roll_map(adapt_map, lh_edge_lon=361 * u.deg) def test_cea_header(): # Assert default reference pixel is at 0 deg lon - cea_default = utils.carr_cea_wcs_header( + cea_default = carr_cea_wcs_header( datetime.datetime(2020, 1, 1), [360, 180] ) assert cea_default['crval1'] == 0.0 # Assert custom reference pixel is expected lon - cea_shift = utils.carr_cea_wcs_header( + cea_shift = carr_cea_wcs_header( datetime.datetime(2020, 1, 1), [360, 180], map_center_longitude=10.0*u.deg @@ -140,14 +136,14 @@ def test_cea_header(): # Test reference pixel shift error handling # 1: No units with pytest.raises(u.UnitTypeError): - cea_default = utils.carr_cea_wcs_header( + cea_default = carr_cea_wcs_header( datetime.datetime(2020, 1, 1), [360, 180], map_center_longitude=0.0 ) # 2: Wrong Units with pytest.raises(u.UnitTypeError): - cea_default = utils.carr_cea_wcs_header( + cea_default = carr_cea_wcs_header( datetime.datetime(2020, 1, 1), [360, 180], map_center_longitude=0.0*u.m diff --git a/sunkit_magex/pfss/tracing.py b/sunkit_magex/pfss/tracing.py index 046483c..5cf6e73 100644 --- a/sunkit_magex/pfss/tracing.py +++ b/sunkit_magex/pfss/tracing.py @@ -2,6 +2,7 @@ import warnings import numpy as np +from streamtracer import StreamTracer, VectorGrid import astropy.constants as const import astropy.coordinates as astrocoords @@ -10,6 +11,7 @@ import sunkit_magex.pfss import sunkit_magex.pfss.fieldline as fieldline +__all__ = ['Tracer', 'PerformanceTracer'] class Tracer(abc.ABC): """ @@ -73,7 +75,7 @@ def coords_to_xyz(seeds, output): return x, y, z -class FortranTracer(Tracer): +class PerformanceTracer(Tracer): r""" Tracer using compiled code via streamtracer. @@ -99,12 +101,6 @@ class FortranTracer(Tracer): directly on the poles will not go anywhere. """ def __init__(self, max_steps='auto', step_size=1): - try: - from streamtracer import StreamTracer - except ModuleNotFoundError as e: - raise RuntimeError( - 'Using this tracer requires the streamtracer module, ' - 'but streamtracer could not be loaded') from e self.max_steps = max_steps self.step_size = step_size self.max_steps = max_steps @@ -116,8 +112,6 @@ def vector_grid(output): """ Create a `streamtracer.VectorGrid` object from an `~sunkit_magex.pfss.Output`. """ - from streamtracer import VectorGrid - # The indexing order on the last index is (phi, s, r) vectors = output.bg.copy() @@ -186,36 +180,3 @@ def trace(self, seeds, output): xs = [np.stack(sunkit_magex.pfss.coords.strum2cart(x[:, 2], x[:, 1], x[:, 0]), axis=-1) for x in xs] flines = [fieldline.FieldLine(x[:, 0], x[:, 1], x[:, 2], output) for x in xs] return fieldline.FieldLines(flines) - - -class PythonTracer(Tracer): - """ - Tracer using native python code. - - Uses `scipy.integrate.solve_ivp`, with an LSODA method. - """ - def __init__(self, atol=1e-4, rtol=1e-4): - """ - dtf : float - Absolute tolerance of the tracing. - rtol : float - Relative tolerance of the tracing. - """ - self.atol = atol - self.rtol = rtol - - def trace(self, seeds, output): - self.validate_seeds(seeds) - x, y, z = self.coords_to_xyz(seeds, output) - seeds = np.atleast_2d(np.stack((x, y, z), axis=-1)) - - flines = [] - for seed in seeds: - xforw = output._integrate_one_way(1, seed, self.rtol, self.atol) - xback = output._integrate_one_way(-1, seed, self.rtol, self.atol) - xback = np.flip(xback, axis=1) - xout = np.vstack((xback.T, xforw.T)) - fline = fieldline.FieldLine(xout[:, 0], xout[:, 1], xout[:, 2], output) - - flines.append(fline) - return fieldline.FieldLines(flines) diff --git a/sunkit_magex/pfss/utils.py b/sunkit_magex/pfss/utils.py index 2e850f2..ed13377 100644 --- a/sunkit_magex/pfss/utils.py +++ b/sunkit_magex/pfss/utils.py @@ -1,11 +1,8 @@ -import os import numpy as np import astropy.constants as const import astropy.coordinates as coord -import astropy.io -import astropy.time from astropy import units as u from astropy.wcs import WCS @@ -13,99 +10,7 @@ import sunpy.map import sunpy.time -__all__ = ['fix_hmi_meta', 'load_adapt', 'carr_cea_wcs_header', 'is_cea_map', 'is_car_map', 'is_full_sun_synoptic_map', 'car_to_cea', 'roll_map'] - -def _observer_coord_meta(observer_coord): - """ - Convert an observer coordinate into FITS metadata. - """ - new_obs_frame = sunpy.coordinates.HeliographicStonyhurst( - obstime=observer_coord.obstime) - observer_coord = observer_coord.transform_to(new_obs_frame) - - new_meta = {'hglt_obs': observer_coord.lat.to_value(u.deg)} - new_meta['hgln_obs'] = observer_coord.lon.to_value(u.deg) - new_meta['dsun_obs'] = observer_coord.radius.to_value(u.m) - return new_meta - - -def _earth_obs_coord_meta(obstime): - """ - Return metadata for an Earth observer coordinate. - """ - return _observer_coord_meta(sunpy.coordinates.get_earth(obstime)) - - -def fix_hmi_meta(hmi_map): - """ - Fix non-compliant FITS metadata in HMI maps. - - This function: - - Corrects CUNIT2 from 'Sine Latitude' to 'deg' - - Corrects CDELT1 and CDELT2 for a CEA projection - - Populates the DATE-OBS keyword from the T_OBS keyword - - Sets the observer coordinate to the Earth - - Notes - ----- - If you have sunpy > 2.1 installed, this function is not needed as sunpy - will automatically make these fixes. - """ - if not isinstance(hmi_map, sunpy.map.sources.HMIMap): - raise ValueError('Input must be of type HMIMap. ' - 'n.b. if you have sunpy 2.1 installed, ' - 'this function is redundant ' - 'as sunpy 2.1 automatically fixes HMI metadata.') - - if hmi_map.meta['cunit1'] == 'Degree': - hmi_map.meta['cunit1'] = 'deg' - - if hmi_map.meta['cunit2'] == 'Sine Latitude': - hmi_map.meta['cunit2'] = 'deg' - - # Since, this map uses the cylindrical equal-area (CEA) projection, - # the spacing should be modified to 180/pi times the original value - # Reference: Section 5.5, Thompson 2006 - hmi_map.meta['cdelt2'] = 180 / np.pi * hmi_map.meta['cdelt2'] - hmi_map.meta['cdelt1'] = np.abs(hmi_map.meta['cdelt1']) - - if 'date-obs' not in hmi_map.meta and 't_obs' in hmi_map.meta: - hmi_map.meta['date-obs'] = sunpy.time.parse_time( - hmi_map.meta['t_obs']).isot - - # Fix observer coordinate - if 'hglt_obs' not in hmi_map.meta: - - hmi_map.meta.update(_earth_obs_coord_meta(hmi_map.meta['date-obs'])) - - -def load_adapt(adapt_path): - """ - Parse adapt .fts file as a `sunpy.map.MapSequence` - - ADAPT magnetograms contain 12 realizations and their data - attribute consists of a 3D data cube where each slice is - the data corresponding to a separate realization of the - magnetogram. This function loads the raw fits file and - parses it to a `sunpy.map.MapSequence` object containing - a `sunpy.map.Map` instance for each realization. - - Parameters - ---------- - adapt_path : `str` - Filepath corresponding to an ADAPT .fts file - - Returns - ------- - `sunpy.map.MapSequence` - """ - adapt_fits = astropy.io.fits.open(adapt_path) - header = adapt_fits[0].header - if header['MODEL'] != 'ADAPT': - raise ValueError(f"{os.path.basename(adapt_path)} header['MODEL'] is not 'ADAPT'") - data_header_pairs = [(map_slice, header) for map_slice in adapt_fits[0].data] - return sunpy.map.Map(data_header_pairs, sequence=True) - +__all__ = ['carr_cea_wcs_header', 'is_cea_map', 'is_car_map', 'is_full_sun_synoptic_map', 'car_to_cea', 'roll_map'] @u.quantity_input def carr_cea_wcs_header(dtime, shape, *, map_center_longitude=0*u.deg): @@ -286,8 +191,11 @@ def car_to_cea(m, method='interp'): -------- :mod:`reproject` for the methods that perform the reprojection. """ - # Add reproject import here to avoid import dependency - from reproject import reproject_adaptive, reproject_exact, reproject_interp + try: + from reproject import reproject_adaptive, reproject_exact, reproject_interp + except ImportError: + raise ImportError('reproject must be installed to use this function') + methods = {'adaptive': reproject_adaptive, 'interp': reproject_interp, 'exact': reproject_exact}