From acd8bf015b8b72bc55dd29f03c57ac362b180b23 Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Wed, 28 Feb 2024 08:24:31 -0800 Subject: [PATCH] Passover --- .gitignore | 7 + .pre-commit-config.yaml | 2 +- .ruff.toml | 10 +- README.rst | 8 +- docs/conf.py | 12 +- docs/index.rst | 5 +- docs/installing.rst | 16 +- ...ical_methods.pdf => numerical-methods.pdf} | Bin 378748 -> 393260 bytes docs/manual/source/numerical-methods.blg | 46 ++++++ docs/manual/source/numerical-methods.tex | 137 +++++++++++++----- docs/numerical_method.rst | 6 +- docs/performance.rst | 20 +-- docs/reference/index.rst | 6 + docs/{ => reference}/pfsspy.rst | 3 - docs/synoptic_fits.rst | 35 +++-- examples/finding_data/adapt_map_sequence.py | 22 +-- examples/finding_data/plot_hmi.py | 21 ++- examples/internals/README.rst | 4 + .../plot_computation_grid.py | 17 ++- .../tracer_performance.py | 29 ++-- examples/pfsspy_info/README.rst | 2 - examples/testing/README.rst | 3 +- examples/testing/open_flux_harmonics.py | 8 +- examples/testing/open_flux_nr.py | 18 ++- examples/testing/plot_error_map.py | 19 +-- examples/testing/plot_harmonic_comparison.py | 8 +- examples/testing/plot_open_flux_harmonics.py | 3 + examples/testing/plot_open_flux_nr.py | 4 +- examples/testing/plot_tracer_step_size.py | 1 - .../using_pfsspy/plot_aia_overplotting.py | 43 +++--- examples/using_pfsspy/plot_dipole.py | 27 ++-- .../plot_field_line_magnetic_field.py | 18 ++- examples/using_pfsspy/plot_gong.py | 35 +++-- examples/using_pfsspy/plot_hmi.py | 26 ++-- examples/using_pfsspy/plot_open_closed_map.py | 26 ++-- examples/utils/plot_car_reproject.py | 21 +-- pyproject.toml | 93 ++++++------ sunkit_magex/__init__.py | 1 + sunkit_magex/data/README.rst | 6 - sunkit_magex/pfss/__init__.py | 31 ++-- sunkit_magex/pfss/analytic.py | 16 +- sunkit_magex/pfss/interpolator.py | 6 +- sunkit_magex/pfss/map.py | 39 +---- sunkit_magex/pfss/pfss.py | 8 +- .../tests/{example_maps.py => conftest.py} | 0 sunkit_magex/pfss/tests/test_analytic.py | 2 +- sunkit_magex/pfss/tests/test_fieldline.py | 12 +- sunkit_magex/pfss/tests/test_map.py | 3 - sunkit_magex/pfss/tests/test_pfss.py | 2 - sunkit_magex/pfss/tests/test_tracers.py | 9 +- sunkit_magex/pfss/tests/test_utils.py | 7 +- sunkit_magex/pfss/tracing.py | 6 +- sunkit_magex/pfss/utils.py | 88 +++-------- sunkit_magex/tests/__init__.py | 4 - tox.ini | 19 +-- 55 files changed, 527 insertions(+), 493 deletions(-) rename docs/manual/{numerical_methods.pdf => numerical-methods.pdf} (84%) create mode 100644 docs/manual/source/numerical-methods.blg create mode 100644 docs/reference/index.rst rename docs/{ => reference}/pfsspy.rst (89%) create mode 100644 examples/internals/README.rst rename examples/{pfsspy_info => internals}/plot_computation_grid.py (86%) rename examples/{pfsspy_info => internals}/tracer_performance.py (79%) delete mode 100644 examples/pfsspy_info/README.rst delete mode 100644 sunkit_magex/data/README.rst rename sunkit_magex/pfss/tests/{example_maps.py => conftest.py} (100%) delete mode 100644 sunkit_magex/tests/__init__.py diff --git a/.gitignore b/.gitignore index 7a8e01b..6613962 100644 --- a/.gitignore +++ b/.gitignore @@ -264,3 +264,10 @@ package.json # repo specific examples/using_pfsspy/aia_map.fits docs/sg_execution_times.rst +docs/manual/source/numerical-methods.aux +docs/manual/source/numerical-methods.bbl +docs/manual/source/numerical-methods.fdb_latexmk +docs/manual/source/numerical-methods.fls +docs/manual/source/numerical-methods.out +docs/manual/source/numerical-methods.pdf +docs/manual/source/numerical-methods.synctex.gz diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab0ad3f..a3fed62 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: # This should be before any formatting hooks like isort - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.2.1" + rev: "v0.2.2" hooks: - id: ruff args: ["--fix"] diff --git a/.ruff.toml b/.ruff.toml index 39a8ff7..1e4c4ce 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -5,24 +5,18 @@ exclude = [ "__pycache__", "build", "sunkit-magex/version.py", + "examples/testing/", ] [lint] select = ["E", "F", "W", "UP", "PT"] extend-ignore = [ # pycodestyle (E, W) - "E501", # LineTooLong # TODO! fix + "E501", # LineTooLong # pytest (PT) "PT001", # Always use pytest.fixture() "PT004", # Fixtures which don't return anything should have leading _ - "PT007", # Parametrize should be lists of tuples # TODO! fix - "PT011", # Too broad exception assert # TODO! fix "PT023", # Always use () on pytest decorators - - # pfsspy - # TODO: Fix these - "E741", - "F811", ] [lint.flake8-tidy-imports] diff --git a/README.rst b/README.rst index ac0e7b6..185edb4 100644 --- a/README.rst +++ b/README.rst @@ -4,11 +4,9 @@ Potential field source surface modelling License ------- -This project is Copyright (c) The SunPy Community and licensed under -the terms of the GNU GPL v3+ license. This package is based upon -the `Openastronomy packaging guide `_ -which is licensed under the BSD 3-clause licence. See the licenses folder for -more information. +This project is Copyright (c) The SunPy Community and licensed under the terms of the GNU GPL v3+ license. +This package is based upon the `Openastronomy packaging guide `_ which is licensed under the BSD 3-clause license. +See the licenses folder for more information. Contributing ------------ diff --git a/docs/conf.py b/docs/conf.py index 2685118..0236ae2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,13 +5,11 @@ # http://www.sphinx-doc.org/en/master/config import os +from sphinx_gallery.sorting import ExplicitOrder +from sunkit_magex import __version__ # -- Project information ----------------------------------------------------- - -# The full version, including alpha/beta/rc tags -from sunkit_magex import __version__ release = __version__ - project = "sunkit-magex" copyright = "2024, The SunPy Community" author = "The SunPy Community" @@ -64,13 +62,9 @@ } # -- Options for HTML output ------------------------------------------------- - html_theme = "sunpy" # -- Sphinx Gallery ---------------------------------------------------------- - -from sphinx_gallery.sorting import ExplicitOrder # noqa - sphinx_gallery_conf = { "ignore_pattern": ".*helpers.py", "examples_dirs": "../examples", @@ -85,7 +79,5 @@ # -- Other options ---------------------------------------------------------- default_role = 'py:obj' - os.environ["JSOC_EMAIL"] = 'jsoc@sunpy.org' - nitpicky = True diff --git a/docs/index.rst b/docs/index.rst index de7855f..447313a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,6 @@ +========================== sunkit-magex Documentation --------------------------- +========================== This is the documentation for sunkit-magex. @@ -9,7 +10,7 @@ This is the documentation for sunkit-magex. installing generated/gallery/index - pfsspy + reference/index performance numerical_method synoptic_fits diff --git a/docs/installing.rst b/docs/installing.rst index 78d2cf5..54275e1 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -1,15 +1,17 @@ +========== Installing ----------- -sunkit_magex can be installed from PyPI using +========== -.. code:: +``sunkit_magex`` can be installed from PyPI using + +.. code:: shell pip install sunkit-magex -This will install sunkit_magex and all of its dependencies. In addition to the core -dependencies, there are two optional dependencies (numba, streamtracer) that -improve code performance. These can be installed with +This will install sunkit_magex and all of its dependencies. +In addition to the core dependencies, there are two optional dependencies (numba, streamtracer) that can improve code performance. +These can be installed with -.. code:: +.. code:: shell pip install sunkit-magex[performance] diff --git a/docs/manual/numerical_methods.pdf b/docs/manual/numerical-methods.pdf similarity index 84% rename from docs/manual/numerical_methods.pdf rename to docs/manual/numerical-methods.pdf index 95d1c2ed1596eed2a7cd3219dd7e125d369ed3ad..6852857e79c4a2f86234c77aa05f469617d13299 100644 GIT binary patch delta 60518 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 diff --git a/docs/manual/source/numerical-methods.blg b/docs/manual/source/numerical-methods.blg new file mode 100644 index 0000000..a2db0cd --- /dev/null +++ b/docs/manual/source/numerical-methods.blg @@ -0,0 +1,46 @@ +This is BibTeX, Version 0.99d (TeX Live 2023/Arch Linux) +Capacity: max_strings=200000, hash_size=200000, hash_prime=170003 +The top-level auxiliary file: numerical-methods.aux +The style file: abbrvnat.bst +Database file #1: refs.bib +You've used 1 entry, + 2773 wiz_defined-function locations, + 600 strings with 4742 characters, +and the built_in function-call counts, 540 in all, are: += -- 46 +> -- 30 +< -- 0 ++ -- 10 +- -- 9 +* -- 50 +:= -- 89 +add.period$ -- 4 +call.type$ -- 1 +change.case$ -- 6 +chr.to.int$ -- 1 +cite$ -- 2 +duplicate$ -- 21 +empty$ -- 41 +format.name$ -- 10 +if$ -- 107 +int.to.chr$ -- 1 +int.to.str$ -- 1 +missing$ -- 1 +newline$ -- 14 +num.names$ -- 4 +pop$ -- 9 +preamble$ -- 1 +purify$ -- 5 +quote$ -- 0 +skip$ -- 15 +stack$ -- 0 +substring$ -- 23 +swap$ -- 2 +text.length$ -- 0 +text.prefix$ -- 0 +top$ -- 0 +type$ -- 11 +warning$ -- 0 +while$ -- 4 +width$ -- 0 +write$ -- 22 diff --git a/docs/manual/source/numerical-methods.tex b/docs/manual/source/numerical-methods.tex index 510bfcf..91b46c3 100644 --- a/docs/manual/source/numerical-methods.tex +++ b/docs/manual/source/numerical-methods.tex @@ -8,16 +8,13 @@ \usepackage[pdftex, pdftitle={My title}, pdflang={en-GB}]{hyperref} \renewcommand{\familydefault}{\sfdefault} -%\usepackage{axessibility} -%\usepackage{accessibility} % - fonts -%\usepackage{tgheros} \usepackage{sansmathfonts} \usepackage[scaled=0.95]{helvet} \renewcommand{\rmdefault}{\sfdefault} -\title{Notes on the Numerical Methods in \texttt{sunkit_magex.pfss}} +\title{Notes on the Numerical Methods in \texttt{sunkit\_magex.pfss}} \author{A. R. Yeates\\ Department of Mathematical Sciences, Durham University, UK} \date{\today} @@ -58,7 +55,7 @@ \newcommand{\OB}{{\cal B}} % Remove paragraph indentation -\setlength{\parindent}{0pc}% +\setlength{\parindent}{0pc} \setlength{\parskip}{\medskipamount} \begin{document} @@ -66,7 +63,8 @@ \section{Basic Equations} -The \texttt{sunkit_magex.pfss} code solves the for a magnetic field satisfying +The \texttt{sunkit\_magex.pfss} code solves the for a magnetic field satisfying + \begin{align} \nabla\times\Bb=0,\qquad \nabla\cdot\Bb = 0 @@ -76,6 +74,7 @@ \section{Basic Equations} &B_r(\theta,\phi) = g(\theta,\phi) \quad \textrm{on} \quad r=1,\\ &B_\theta=B_\phi=0 \quad \textrm{on} \quad r=R_{ss}. \label{eqn:bc1} \end{align} + The function $g(\theta,\phi)$ is specified by the user. \section{Numerical Grid} @@ -90,24 +89,29 @@ \section{Numerical Grid} \[ \rho = \ln(r), \quad s=\cos\theta \] -in terms of spherical coordinates $(r,\theta\,\phi)$. The coordinate scale factors $|\mathrm{d}\boldsymbol r/\mathrm{d}\rho|$, $|\mathrm{d}\boldsymbol r/\mathrm{d}s|$, $|\mathrm{d}\boldsymbol r/\mathrm{d}\phi|$ are +in terms of spherical coordinates $(r,\theta\,\phi)$. +The coordinate scale factors $|\mathrm{d}\boldsymbol r/\mathrm{d}\rho|$, $|\mathrm{d}\boldsymbol r/\mathrm{d}s|$, $|\mathrm{d}\boldsymbol r/\mathrm{d}\phi|$ are \[ h_\rho = r = \mathrm{e}^\rho,\quad h_s = \frac{r}{\sin\theta} = \frac{\mathrm{e}^\rho}{\sqrt{1-s^2}}, \quad h_\phi = r\sin\theta = \mathrm{e}^\rho\sqrt{1-s^2}. \] -The grid is illustrated in Figure \ref{fig:grid}. Note that the longitudinal cell size goes to zero at the poles; these points are treated specially in the calculation of $\Bb$. Note also that, since $s$ is a decreasing function of $\theta$, the components of a vector $\boldsymbol{v}$ in $(\rho,s,\phi)$ are $v_\rho = v_r$ but $v_s = -v_\theta$. +The grid is illustrated in Figure \ref{fig:grid}. +Note that the longitudinal cell size goes to zero at the poles; these points are treated specially in the calculation of $\Bb$. +Note also that, since $s$ is a decreasing function of $\theta$, the components of a vector $\boldsymbol{v}$ in $(\rho,s,\phi)$ are $v_\rho = v_r$ but $v_s = -v_\theta$. We define the number of grid cells $\nr$, $\ns$, $\nph$, with corresponding uniform spacings \[ \dr = \frac{\ln(R_{ss})}{\nr}, \quad \ds = \frac{2}{\ns}, \quad \dph = \frac{2\pi}{\nph}. \] -Note that the boundaries in $s$ are at the poles $s=\pm1$, at which points $h_\phi$ is not defined. The solution is periodic in the longitudinal ($\phi$) direction. - +Note that the boundaries in $s$ are at the poles $s=\pm1$, at which points $h_\phi$ is not defined. +The solution is periodic in the longitudinal ($\phi$) direction. \section{Numerical method} \subsection{Overall strategy} -Rather than writing $\Bb = \nabla\chi$ and solving $\nabla^2\chi=0$, we write instead $\Ab = \nabla\times\big(\psi \evr\big)$. Then accounting for the unusual coordinates we get +Rather than writing $\Bb = \nabla\chi$ and solving $\nabla^2\chi=0$, we write instead $\Ab = \nabla\times\big(\psi \evr\big)$. +Then accounting for the unusual coordinates we get + \begin{align} \Bb &= \frac{1}{h_\rho h_sh_\phi} \begin{vmatrix} @@ -118,68 +122,90 @@ \subsection{Overall strategy} &= -\frac{1}{h_sh_\phi}\left[\dy_s\left(\frac{h_\phi}{h_s}\dy_s\psi\right) + \dy_\phi\left(\frac{h_s}{h_\phi}\dy_\phi\psi\right) \right]\evr + \frac{1}{h_\rho h_\phi}\dy_\phi\dy_\rho\psi\evp + \frac{1}{h_\rho h_s}\dy_s\dy_\rho\psi\evs\\ &= -\Delta_\perp\psi\evr + \frac{1}{h_\phi}\dy_\phi\left(\frac{1}{h_\rho}\dy_\rho\psi\right)\evp + \frac{1}{h_s}\dy_s\left(\frac{1}{h_\rho}\dy_\rho\psi\right)\evs. \end{align} + This will take the curl-free form $\Bb = \nabla\big(\tfrac1{h_\rho}\dy_\rho\psi\big)$ provided that + \begin{equation} \nabla^2_\perp\psi = -\frac{1}{h_\rho}\dy_\rho\left(\frac{1}{h_\rho}\dy_\rho\psi\right), \label{eqn:psi} \end{equation} -so our strategy is to solve Equation \eqref{eqn:psi} for $\psi$, then reconstruct $\Ab$ and $\Bb$. The reason for doing it this way is that it allows us to compute $\Ab$ as well as $\Bb$ (again, for legacy reasons). + +so our strategy is to solve Equation \eqref{eqn:psi} for $\psi$, then reconstruct $\Ab$ and $\Bb$. +The reason for doing it this way is that it allows us to compute $\Ab$ as well as $\Bb$ (again, for legacy reasons). \subsection{Numerical solution} -We follow the method described in \cite{vanballe2000}, except that we modify the finite-difference discretisation to suit our particular coordinates. +We follow the method described in \cite{vanballe2000}, except that we modify the finite-difference discretization to suit our particular coordinates. -The discretisation is chosen so that we will have $\nabla\times\Bb=0$ to machine precision on a staggered grid, when the curl is taken using central differences. This property of essentially zero current density is required when using the PFSS solution to, e.g., initialize non-potential simulations. It would not typically be achieved by interpolating a spherical harmonic solution onto the numerical grid. However, we will see that the discrete solution effectively computes discrete approximations of the spherical harmonics, tailored to our particular difference formula. +The discretization is chosen so that we will have $\nabla\times\Bb=0$ to machine precision on a staggered grid, when the curl is taken using central differences. +This property of essentially zero current density is required when using the PFSS solution to, e.g., initialize non-potential simulations. +It would not typically be achieved by interpolating a spherical harmonic solution onto the numerical grid. +However, we will see that the discrete solution effectively computes discrete approximations of the spherical harmonics, tailored to our particular difference formula. In the following subsections, we describe the numerical solution in more detail. \subsubsection{Variables} Let the coordinate grid points be defined by + \begin{align*} &\rho^k = k\dr, \qquad k=0,\ldots, n_\rho,\\ &s^j = j\ds - 1, \qquad j=0,\ldots, n_s,\\ &\phi^i = i\dph, \qquad i=0,\ldots, n_\phi. \end{align*} -\textcolor{blue}{In the code the first two arrays are called \texttt{rg} and \texttt{sg} (that for \texttt{pg} is not required). There are also arrays \texttt{rc}, \texttt{sc} and \texttt{pc} corresponding to the cell centres, i.e. $\rho^{k+1/2}$, $s^{j+1/2}$ and $\phi^{i+1/2}$.} + +\textcolor{blue}{In the code the first two arrays are called \texttt{rg} and \texttt{sg} (that for \texttt{pg} is not required). +There are also arrays \texttt{rc}, \texttt{sc} and \texttt{pc} corresponding to the cell centres, i.e. $\rho^{k+1/2}$, $s^{j+1/2}$ and $\phi^{i+1/2}$.} To deal with the curvilinear coordinates, we define the edge lengths + \begin{align*} &L_\rho^{k+\half,j,i} = \int_{\rho^k}^{\rho^{k+1}} h_\rho\,\mathrm{d}\rho = \ex^{\rho^{k+1}} - \ex^{\rho^k},\\ &L_s^{k,j+\half,i} = \int_{s^j}^{s^{j+1}} h_s\,\mathrm{d}s = \ex^{\rho^k}\big(\arcsin(s^{j+1}) - \arcsin(s^j)\big),\\ &L_\phi^{k,j,i+\half} = \int_{\phi^i}^{\phi^{i+1}} h_\phi\,\mathrm{d}\phi = \ex^{\rho^k}\sigma^j\dph. \end{align*} + Here we used the fact that $\dr$, $\ds$ and $\dph$ are constant, and used the shorthand \[ \sigma^j := \sqrt{1 - (s^j)^2}. \] Similarly we define the areas of the cell faces + \begin{align*} &S_\rho^{k,j+\half,i+\half} = \int_{\phi^i}^{\phi^{i+1}}\int_{s^j}^{s^{j+1}} h_s h_\phi\,\mathrm{d}s\mathrm{d}\phi = \ex^{2\rho^k}\ds\dph,\\ &S_s^{k+\half,j,i+\half} = \int_{\rho^k}^{\rho^{k+1}}\int_{\phi^i}^{\phi^{i+1}} h_\rho h_\phi\,\mathrm{d}\phi\mathrm{d}\rho = \tfrac12\big(\ex^{2\rho^{k+1}} - \ex^{2\rho^{k}}\big)\sigma^j\,\dph,\\ &S_\phi^{k+\half,j+\half,i} = \int_{\rho^k}^{\rho^{k+1}}\int_{s^j}^{s^{j+1}}h_\rho h_s\,\mathrm{d}s\mathrm{d}\rho = \tfrac12\big(\ex^{2\rho^{k+1}}- \ex^{2\rho^k}\big)\big(\arcsin(s^{j+1}) - \arcsin(s^j)\big). \end{align*} + \textcolor{blue}{In the code the face areas are stored in arrays \texttt{Sbr}, \texttt{Sbs} and \texttt{Sbp} (with only the dimensions required).} -In the code the magnetic field $\Bb$ is defined staggered on the face centres, so $B_\rho^{k,j+\half,i+\half}$, $B_s^{k+\half,j,i+\half}$, $B_\phi^{k+\half,j+\half,i}$. \textcolor{blue}{These variables are called \texttt{br}, \texttt{bs} and \texttt{bp}.} +In the code the magnetic field $\Bb$ is defined staggered on the face centres, so $B_\rho^{k,j+\half,i+\half}$, $B_s^{k+\half,j,i+\half}$, $B_\phi^{k+\half,j+\half,i}$. +\textcolor{blue}{These variables are called \texttt{br}, \texttt{bs} and \texttt{bp}.} -The vector potential is located on the corresponding cell edges, so $A_\rho^{k+\half,j,i}$, $A_s^{k,j+\half,i+\half}$, $A_\phi^{k,j,i+\half}$. In fact, these values are never required on their own, only multiplied by the corresponding edge lengths. \textcolor{blue}{So the variables \texttt{alr}, \texttt{als} and \texttt{alp} correspond to the products $L_\rho A_\rho$, $L_sA_s$ and $L_\phi A_\phi$, respectively.} +The vector potential is located on the corresponding cell edges, so $A_\rho^{k+\half,j,i}$, $A_s^{k,j+\half,i+\half}$, $A_\phi^{k,j,i+\half}$. +In fact, these values are never required on their own, only multiplied by the corresponding edge lengths. +\textcolor{blue}{So the variables \texttt{alr}, \texttt{als} and \texttt{alp} correspond to the products $L_\rho A_\rho$, $L_sA_s$ and $L_\phi A_\phi$, respectively.} -Finally, the potential $\psi$ is located on the $\rho$-faces (like $B_\rho$), so $\psi^{k,j+\half,i+\half}$. \textcolor{blue}{It is stored in the variable \texttt{psi}.} +Finally, the potential $\psi$ is located on the $\rho$-faces (like $B_\rho$), so $\psi^{k,j+\half,i+\half}$. +\textcolor{blue}{It is stored in the variable \texttt{psi}.} \subsubsection{Derivatives} Firstly, we have $\Ab = \nabla\times\big(\psi\evr\big)$. Numerically, this is approximated by + \begin{equation} A_s^{k,j+\half,i} = -\frac{\psi^{k,j+\half,i+\half} - \psi^{k,j+\half,i-\half}}{L_\phi^{k,j+\half,i}}, \qquad A_\phi^{k,j,i+\half} = \frac{\psi^{k,j+\half,i+\half} - \psi^{k,j-\half,i+\half}}{L_s^{k,j,i+\half}}. \label{eqn:as} \end{equation} + The magnetic field $\Bb = \nabla\times\Ab$ is then approximated by + \begin{align} &(S_\rho B_\rho)^{k,j+\half,i+\half} = (L_s A_s)^{k,j+\half,i+1} - (L_s A_s)^{k,j+\half,i} - (L_\phi A_\phi)^{k,j+1,i+\half} + (L_\phi A_\phi)^{k,j,i+\half},\\ &(S_s B_s)^{k+\half,j,i+\half} = (L_\phi A_\phi)^{k+1,j,i+\half} - (L_\phi A_\phi)^{k,j,i+\half},\\ &(S_\phi B_\phi)^{k+\half,j+\half,i} = - (L_s A_s)^{k+1,j+\half,i} + (L_s A_s)^{k,j+\half,i}. \label{eqn:bp} \end{align} + These formulae correspond to Stokes' Theorem applied to the cell face. The condition $\nabla\times\Bb=0$ may be expressed similarly as \begin{align} @@ -189,32 +215,41 @@ \subsubsection{Derivatives} \end{align} Note that the factors $L_\rho$, $L_s$, $L_\phi$ here are defined normal to the cell faces, not on the edges. But they have the same formulae. -In fact, condition \eqref{eqn:j1} is automatically satisfied. This may be shown using equations \eqref{eqn:as} to \eqref{eqn:bp}, together with our formulae for $L_s$, $L_\phi$, $S_s$ and $S_\phi$. +In fact, condition \eqref{eqn:j1} is automatically satisfied. +This may be shown using equations \eqref{eqn:as} to \eqref{eqn:bp}, together with our formulae for $L_s$, $L_\phi$, $S_s$ and $S_\phi$. -Below, we will discretise \eqref{eqn:psi} in such a way that conditions \eqref{eqn:j2} and \eqref{eqn:j3} are also satisfied exactly (up to rounding error). +Below, we will discretize \eqref{eqn:psi} in such a way that conditions \eqref{eqn:j2} and \eqref{eqn:j3} are also satisfied exactly (up to rounding error). \subsubsection{Boundary conditions for $\Bb$} -Boundary conditions are needed when \texttt{br}, \texttt{bs}, \texttt{bp} are averaged to the grid points for output. We use a layer of ``ghost cells'', whose values are set by the following boundary conditions: +Boundary conditions are needed when \texttt{br}, \texttt{bs}, \texttt{bp} are averaged to the grid points for output. +We use a layer of ``ghost cells'', whose values are set by the following boundary conditions: + \begin{enumerate} \item In $\phi$, \texttt{br} and \texttt{bs} are simply periodic. \item At the outer boundary $\rho=\log(R_{ss})$, ghost values of \texttt{bs} and \texttt{bp} are set assuming constant gradient in $\rho$. \item At the inner boundary, $\rho=0$, ghost values of \texttt{bs} and \texttt{bp} are set using equations \eqref{eqn:j2} and \eqref{eqn:j3} (effectively assuming zero horizontal current density). -\item At the polar boundaries, the ghost value of \texttt{br} is set to the polemost interior value from the opposite side of the grid. Similarly, \texttt{bp} is set to minus the polemost interior value from the opposite side of the grid. The values of \texttt{bs} at the poles are not meaningful as the cell faces have zero area. However, they are set to the average of the two neighbouring interior values at that longitude (with the opposite one being reversed in sign). +\item At the polar boundaries, the ghost value of \texttt{br} is set to the polemost interior value from the opposite side of the grid. + Similarly, \texttt{bp} is set to minus the polemost interior value from the opposite side of the grid. + The values of \texttt{bs} at the poles are not meaningful as the cell faces have zero area. + However, they are set to the average of the two neighbouring interior values at that longitude (with the opposite one being reversed in sign). \end{enumerate} + Some of these conditions are chosen for compatibility with other codes, and are not necessarily the most straightforward option for a pure PFSS solver. \subsubsection{Discretization of Equation \eqref{eqn:psi}} First, we approximate the two-dimensional Laplacian $\nabla^2_\perp\psi$ by + \begin{align*} &\nabla^2_\perp\psi^{k,j+\half,i+\half} = \frac{1}{S_\rho^{k,j+\half,i+\half}}\left[ \frac{L_s^{k,j+\half,i+1}}{L_\phi^{k,j+\half,i+1}}\big(\psi^{k,j+\half,i+\thr} - \psi^{k,j+\half,i+\half}\big) - \frac{L_s^{k,j+\half,i}}{L_\phi^{k,j+\half,i}}\big(\psi^{k,j+\half,i+\half} - \psi^{k,j+\half,i-\half}\big) \right.\nonumber\\ &\left. + - \frac{L_\phi^{k,j+1,i+\half}}{L_s^{k,j+1,i+\half}}\big(\psi^{k,j+\thr,i+\half} - \psi^{k,j+\half,i+\half}\big) - \frac{L_\phi^{k,j,i+\half}}{L_s^{k,j,i+\half}}\big(\psi^{k,j+\half,i+\half} - \psi^{k,j-\half,i+\half}\big) - \right] +\frac{L_\phi^{k,j+1,i+\half}}{L_s^{k,j+1,i+\half}}\big(\psi^{k,j+\thr,i+\half} - \psi^{k,j+\half,i+\half}\big) - \frac{L_\phi^{k,j,i+\half}}{L_s^{k,j,i+\half}}\big(\psi^{k,j+\half,i+\half} - \psi^{k,j-\half,i+\half}\big) +\right] \end{align*} + As shorthand we define the quantities \[ U^{j+\half} = \left(\frac{L_s}{\ds\dph L_\phi}\right)^{j+\half}, \qquad V^j = \left(\frac{L_\phi}{\ds\dph L_s}\right)^j, @@ -222,66 +257,90 @@ \subsubsection{Discretization of Equation \eqref{eqn:psi}} noting that these both depend on $j$ only. \textcolor{blue}{In the code these are called \texttt{Uc} and \texttt{Vg}.} Then we can write our discretization as \begin{align} \nabla^2_\perp\psi^{k,j+\half,i+\half} = \frac{1}{\ex^{2\rho^k}}\Big[U^{j+\half}\big(\psi^{k,j+\half,i+\thr} - \psi^{k,j+\half,i-\half} \big) + V^{j+1}\psi^{k,j+\thr,i+\half} + V^{j}\psi^{k,j-\half,i+\half}\nonumber\\ - - \Big(2U^{j+\half} + V^{j+1} + V^{j}\Big)\psi^{k,j+\half,i+\half}\Big]. - \label{eqn:lapl} +- \Big(2U^{j+\half} + V^{j+1} + V^{j}\Big)\psi^{k,j+\half,i+\half}\Big]. +\label{eqn:lapl} \end{align} + This is the left-hand side of \eqref{eqn:psi}. To discretize the right-hand side of \eqref{eqn:psi}, we use the approximation + \begin{align} -\frac{1}{h_\rho}\dy_\rho\left(\frac{1}{h_\rho}\dy_\rho\psi\right)^{k,j+\half,i+\half} = -\frac{c(\dr)}{L_\rho^{k,j+\half,i+\half}}\left(\frac{\psi^{k+1,j+\half,i+\half} - \psi^{k,j+\half,i+\half}}{L_\rho^{k+\half,j+\half,i+\half}} - \frac{\psi^{k,j+\half,i+\half} - \psi^{k-1,j+\half,i+\half}}{L_\rho^{k-\half,j+\half,i+\half}} \right), \end{align} + where \[ c(\dr) = \frac{2\ex^{\dr/2}}{\ex^{\dr} + 1} = \mathrm{sech}\left(\frac{\dr}{2}\right). \] + Combining this with \eqref{eqn:lapl}, we arrive at + \begin{align} U^{j+\half}\big(\psi^{k,j+\half,i+\thr} - \psi^{k,j+\half,i-\half} \big) + V^{j+1}\psi^{k,j+\thr,i+\half} + V^{j}\psi^{k,j-\half,i+\half} - - \Big(2U^{j+\half} + V^{j+1} + V^{j}\Big)\psi^{k,j+\half,i+\half}\nonumber\\ - = -\frac{c(\dr)\ex^{2\rho^k}}{L_\rho^{k,j+\half,i+\half}}\left(\frac{\psi^{k+1,j+\half,i+\half} - \psi^{k,j+\half,i+\half}}{L_\rho^{k+\half,j+\half,i+\half}} - \frac{\psi^{k,j+\half,i+\half} - \psi^{k-1,j+\half,i+\half}}{L_\rho^{k-\half,j+\half,i+\half}} \right). - \label{eqn:main} +- \Big(2U^{j+\half} + V^{j+1} + V^{j}\Big)\psi^{k,j+\half,i+\half}\nonumber\\ += -\frac{c(\dr)\ex^{2\rho^k}}{L_\rho^{k,j+\half,i+\half}}\left(\frac{\psi^{k+1,j+\half,i+\half} - \psi^{k,j+\half,i+\half}}{L_\rho^{k+\half,j+\half,i+\half}} - \frac{\psi^{k,j+\half,i+\half} - \psi^{k-1,j+\half,i+\half}}{L_\rho^{k-\half,j+\half,i+\half}} \right). +\label{eqn:main} \end{align} + The reader may verify algebraically that conditions \eqref{eqn:j2} and \eqref{eqn:j3} follow if this finite-difference equation is satisfied. \subsubsection{Method of solution} -Equation \eqref{eqn:main}, together with the appropriate boundary conditions, yields a large (but sparse) system of $\nr\ns\nph\times \nr\ns\nph$ linear equations to solve. Fortunately, following \cite{vanballe2000}, we can reduce this to a series of symmetric tridiagonal eigenvalue problems, if we look for eigenfunctions of the form +Equation \eqref{eqn:main}, together with the appropriate boundary conditions, yields a large (but sparse) system of $\nr\ns\nph\times \nr\ns\nph$ linear equations to solve. +Fortunately, following \cite{vanballe2000}, we can reduce this to a series of symmetric tridiagonal eigenvalue problems, if we look for eigenfunctions of the form + \begin{equation} \psi^{k,j+\half,i+\half} = f^kQ_{lm}^{j+\half}\,\ex^{2\pi I mi/\nph}. \label{eqn:eig} \end{equation} -Here the $k$ in $f^k$ is a power, not an index, and $I$ is the square root of $-1$ (since we already used $i$ and $j$ for indices). This reduction will enable very efficient solution of the linear system. + +Here the $k$ in $f^k$ is a power, not an index, and $I$ is the square root of $-1$ (since we already used $i$ and $j$ for indices). +This reduction will enable very efficient solution of the linear system. Substituting \eqref{eqn:eig} in Equation \eqref{eqn:main} gives + \begin{align} -V^{j}Q^{j-\half}_{lm} + \left(V^{j} + V^{j+1}+ 4U^{j+\half}\sin^2\left(\tfrac{\pi m}{\nph}\right) \right)Q^{j+\half}_{lm} - V^{j+1}Q^{j+\thr}_{lm} \nonumber\\ = \frac{c(\dr)\mathrm{e}^{2\rho^k}}{L_\rho^{k,j+\half,i+\half}}\left(\frac{f - 1}{L_\rho^{k+\half,j+\half,i+\half}} - \frac{1 - f^{-1}}{L_\rho^{k-\half,j+\half,i+\half}} \right)Q_{lm}^{j+\half}. \end{align} + The right-hand side can be simplified since the dependence on $k$ cancels out. This leaves the tridiagonal eigenvalue problem + \begin{equation} -V^{j}Q^{j-\half}_{lm} + \left(V^{j} + V^{j+1}+ 4U^{j+\half}\sin^2\left(\tfrac{\pi m}{\nph}\right) \right)Q^{j+\half}_{lm} - V^{j+1}Q^{j+\thr}_{lm} = \lambda_{lm}Q_{lm}^{j+\half}, \end{equation} + where $f$ is determined from the eigenvalues $\lambda_{lm}$ by solving the quadratic relation + \begin{equation} \lambda_{lm} = \frac{c(\dr)}{\mathrm{e}^{\dr/2} - \mathrm{e}^{-\dr/2}} \left(\frac{1-f^{-1}}{1-\mathrm{e}^{-\dr}} - \frac{f-1}{\mathrm{e}^{\dr} - 1}\right). \end{equation} + This may be rearranged into the form + \begin{equation} f^2 - \left[1 + \mathrm{e}^{\dr} + \mathrm{sech}\left(\frac{\dr}{2}\right)\lambda_{lm}(\mathrm{e}^{\dr}-1)(\mathrm{e}^{\dr/2} - \mathrm{e}^{-\dr/2}) \right]f + \mathrm{e}^{\dr} = 0, \end{equation} + with two solutions for each $l$, $m$ given by + \begin{equation} f_{lm}^+, f_{lm}^- = F_{lm} \pm \sqrt{F_{lm}^2 - \mathrm{e}^{\dr}}, \quad \textrm{where} \quad F_{lm} = \tfrac12\Big[1 + \mathrm{e}^{\dr} + \lambda_{lm}(\mathrm{e}^{\dr}-1)\sinh(\dr) \Big]. \end{equation} -\textcolor{blue}{In the code, the eigenvalues are called \texttt{lam} and the corresponding matrix of eigenvectors is \texttt{Q}. The solutions $f_{lm}^+$ and $f_{lm}^-$ are called \texttt{ffp} and \texttt{ffm} respectively.} + +\textcolor{blue}{In the code, the eigenvalues are called \texttt{lam} and the corresponding matrix of eigenvectors is \texttt{Q}. +The solutions $f_{lm}^+$ and $f_{lm}^-$ are called \texttt{ffp} and \texttt{ffm} respectively.} The solution may then be written as a sum of these two sets of radial eigenfunctions: + \begin{equation} \psi^{k,j+\half,i+\half} = \sum_{l=0}^{\ns-1}\sum_{m=0}^{\nph-1}\Big[c_{lm}(f_{lm}^+)^k + d_{lm}(f_{lm}^-)^k) \Big] Q_{lm}^{j+\half}\ex^{2\pi I mi/\nph}. \label{eqn:psisum} \end{equation} + The coefficients $c_{lm}$ and $d_{lm}$ are then determined by the radial boundary conditions: + \begin{enumerate} \item At the inner boundary $\rho = 0$, where $k=0$, we want $B_\rho = -\nabla^2_\perp\psi$ to match our given distribution $g^{j+\half,i+\half}$. We have @@ -298,32 +357,41 @@ \subsubsection{Method of solution} \Big[c_{lm} + d_{lm}\Big] Q_{lm}^{j+\half}\,\mathrm{e}^{2\pi I mi/\nph} = \sum_{m=0}^{\nph-1}b_m^{j+\half}\,\mathrm{e}^{2\pi I mi/\nph}. \] Then the orthonormality of $Q_{lm}^{j+\half}$ for different $l$ allows us to determine + \begin{equation} c_{lm} + d_{lm} = \frac{1}{\lambda_{lm}}\sum_{j=0}^{\ns-1}b_m^{j+\half}Q_{lm}^{j+\half}. \label{eqn:dc2} \end{equation} + \item At the source (outer) surface $\rho=\ln(R_{ss})$, where $k=\nr$, there are two options. + \begin{enumerate} \item \emph{Radial field.} We impose $\dy_\rho\psi = 0$, in the form $\psi^{\nr,j+\half,i+\half}=\psi^{\nr-1,j+\half,i+\half}$, which gives + \begin{equation} \frac{d_{lm}}{c_{lm}} = \frac{(f_{lm}^+)^{\nr} - (f_{lm}^+)^{\nr-1}}{(f_{lm}^-)^{\nr-1} - (f_{lm}^-)^{\nr}}. \label{eqn:dc1} \end{equation} + (Numerically it is better to compute this ratio the other way up, to prevent overflow.) \item \emph{Imposed $B_r$.} In this case the boundary condition is treated similarly to the inner boundary. We require \[ \hat{g}^{j+\half,i+\half} = \sum_{l=0}^{\ns-1}\sum_{m=0}^{\nph-1}\frac{\lambda_{lm}}{\mathrm{e}^{2\rho^{n_\rho}}}\Big[c_{lm}(f_{lm}^+)^{n_\rho} + d_{lm}(f_{lm}^-)^{n_\rho}\Big] Q_{lm}^{j+\half}\mathrm{e}^{2\pi I mi/\nph}, \] so we may again take the discrete Fourier transform to end up with + \begin{equation} c_{lm}(f_{lm}^+)^{n_\rho} + d_{lm}(f_{lm}^-)^{n_\rho} = \frac{\mathrm{e}^{2\rho^{n_\rho}}}{\lambda_{lm}}\sum_{j=0}^{\ns-1}\hat{b}_m^{j+\half}Q_{lm}^{j+\half}, \label{eqn:dc3} \end{equation} \end{enumerate} \end{enumerate} -Solving \eqref{eqn:dc1} simultaneously with either \eqref{eqn:dc2} or \eqref{eqn:dc3} gives $c_{lm}$ and $d_{lm}$. \textcolor{blue}{These are called \texttt{clm} and \texttt{dlm} in the code.} -Remark: as we increase the grid resolution, the eigenfunctions $Q_{lm}^{j+\half}$, which are functions of $\theta$, should converge to the corresponding associated Legendre polynomials $P_l^m(\cos\theta)$, up to normalization. This is illustrated in Figure \ref{fig:Q}. +Solving \eqref{eqn:dc1} simultaneously with either \eqref{eqn:dc2} or \eqref{eqn:dc3} gives $c_{lm}$ and $d_{lm}$. +\textcolor{blue}{These are called \texttt{clm} and \texttt{dlm} in the code.} + +Remark: as we increase the grid resolution, the eigenfunctions $Q_{lm}^{j+\half}$, which are functions of $\theta$, should converge to the corresponding associated Legendre polynomials $P_l^m(\cos\theta)$, up to normalization. +This is illustrated in Figure \ref{fig:Q}. \begin{figure} \centering @@ -331,7 +399,6 @@ \subsubsection{Method of solution} \caption{Comparison of $P_l^m(\cos\theta)$ (coloured lines) with the discrete eigenfunctions $Q_{lm}^{j+\half}$ (black dots), for $m=6$ and $l=0,\ldots,4$, at resolution $\ns=60$ and $\nph=120$. \label{fig:Q}} \end{figure} - \bibliography{refs} \end{document} diff --git a/docs/numerical_method.rst b/docs/numerical_method.rst index f5228ff..4016c7f 100644 --- a/docs/numerical_method.rst +++ b/docs/numerical_method.rst @@ -1,5 +1,5 @@ +================= Numerical methods ------------------ +================= -For more information on the numerical methods used in the PFSS calculation see -:download:`this document `. +For more information on the numerical methods used in the PFSS calculation see :download:`this document `. diff --git a/docs/performance.rst b/docs/performance.rst index 3db7507..482ae81 100644 --- a/docs/performance.rst +++ b/docs/performance.rst @@ -1,19 +1,19 @@ +===================== Improving performance ---------------------- +===================== numba -~~~~~ -sunkit_magex.pfss automatically detects an installation of `numba`_, which compiles -some of the numerical code to speed up pfss calculations. To enable this -simply `install numba`_ and use sunkit_magex.pfss as normal. +----- + +`sunkit_magex.pfss` automatically detects an installation of `numba`_, which compiles some of the numerical code to speed up the pfss calculations. +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 FORTRAN `sunkit_magex.pfss.tracing.FortranTracer`. The FORTRAN version is -significantly faster, using the `streamtracer`_ package. +------------------ +`sunkit_magex.pfss` has two streamline tracers: a pure python `sunkit_magex.pfss.tracing.PythonTracer` and a FORTRAN `sunkit_magex.pfss.tracing.FortranTracer`. +The FORTRAN version is significantly faster, 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/dstansby/streamtracer +.. _streamtracer: https://github.com/sunpy/streamtracer diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 0000000..3801853 --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,6 @@ +API reference +============= + +.. contents:: + + pfsspy diff --git a/docs/pfsspy.rst b/docs/reference/pfsspy.rst similarity index 89% rename from docs/pfsspy.rst rename to docs/reference/pfsspy.rst index 5604d8e..16f6ec3 100644 --- a/docs/pfsspy.rst +++ b/docs/reference/pfsspy.rst @@ -1,6 +1,3 @@ -API reference -============= - .. automodapi:: sunkit_magex.pfss .. automodapi:: sunkit_magex.pfss.grid diff --git a/docs/synoptic_fits.rst b/docs/synoptic_fits.rst index eca9e4a..8c5f4e5 100644 --- a/docs/synoptic_fits.rst +++ b/docs/synoptic_fits.rst @@ -1,29 +1,29 @@ +============================= Synoptic map FITS conventions ============================= -FITS is the most common filetype used for the storing of solar images. On this -page the FITS metadata conventions for synoptic maps are collected. All of this -information can be found in, and is taken from, -"Coordinate systems for solar image data (Thompson, 2005)". +FITS is the most common filetype used for the storing of solar images. +On this page the FITS metadata conventions for synoptic maps are collected. +All of this information can be found in, and is taken from `"Coordinate systems for solar image data (Thompson, 2005)" `__. -================= ====== - Keyword Output ------------------ ------ -CRPIX\ *n* Reference pixel to subtract along axis *n*. Counts from 1 to N. - Integer values refer to the centre of the pixel. -CRVAL\ *n* Coordinate value of the reference pixel along axis *n*. -CDELT\ *n* Pixel spacing along axis *n*. -CTYPE\ *n* Coordinate axis label for axis *n*. -PV\ *i*\_\ *m* Additional parameters needed for some coordinate systems. -================= ====== +================= ====== + Keyword Output +----------------- ------ +CRPIX\ *n* Reference pixel to subtract along axis *n*. Counts from 1 to N. + Integer values refer to the centre of the pixel. +CRVAL\ *n* Coordinate value of the reference pixel along axis *n*. +CDELT\ *n* Pixel spacing along axis *n*. +CTYPE\ *n* Coordinate axis label for axis *n*. +PV\ *i*\_\ *m* Additional parameters needed for some coordinate systems. +================= ====== Note that *CROTAn* is ignored in this short guide. Cylindrical equal area projection --------------------------------- + In this projection, the latitude pixels are equally spaced in sin(latitude). -The reference pixel has to be on the equator, to facilitate alignment with the -solar rotation axis. +The reference pixel has to be on the equator, to facilitate alignment with the solar rotation axis. - CDELT2 is set to 180/pi times the pixel spacing in sin(latitude). - CTYPE1 is either 'HGLN-CEA' or 'CRLN-CEA'. @@ -32,5 +32,4 @@ solar rotation axis. - LONPOLE is 0. The abbreviations are "Heliographic Longitude - Cylindrical Equal Area" etc. -If the system is heliographic the observer must also be defined in the -metadata. +If the system is heliographic the observer must also be defined in the metadata. diff --git a/examples/finding_data/adapt_map_sequence.py b/examples/finding_data/adapt_map_sequence.py index 9ad4b52..276d124 100644 --- a/examples/finding_data/adapt_map_sequence.py +++ b/examples/finding_data/adapt_map_sequence.py @@ -9,11 +9,12 @@ import sunpy.map from matplotlib import gridspec -from sunkit_magex.pfss.sample_data import get_adapt_map +from sunkit_magex import pfss ############################################################################### -# Load an example ADAPT fits file, utility stored in adapt_helpers.py -adapt_fname = get_adapt_map() +# Load an example ADAPT fits file. + +adapt_fname = pfss.sample_data.get_adapt_map() ############################################################################### # ADAPT synoptic magnetograms contain 12 realizations of synoptic magnetograms @@ -27,6 +28,7 @@ # and PFSS solutions generated from them. # # We first read in the fits file using `sunpy.io` : + adapt_fits = sunpy.io.fits.read(adapt_fname) ############################################################################### @@ -34,13 +36,13 @@ # the 12 realizations data and a header with sufficient information to build # the `~sunpy.map.MapSequence`. We unpack this ``HDPair`` into a list of # ``(data,header)`` tuples where ``data`` are the different adapt realizations. -data_header_pairs = [(map_slice, adapt_fits[0].header) - for map_slice in adapt_fits[0].data] +data_header_pairs = [(map_slice, adapt_fits[0].header) for map_slice in adapt_fits[0].data] ############################################################################### # Next, pass this list of tuples as the argument to `sunpy.map.Map` to create -# the map sequence : +# the map sequence: + adapt_maps = sunpy.map.Map(data_header_pairs, sequence=True) ############################################################################### @@ -49,13 +51,13 @@ # returns instances of # ``sunpy.visualization.MapSequenceAnimator`` and # ``matplotlib.animation.FuncAnimation1``. Here, we generate a static -# plot accessing the individual maps in turn : +# plot accessing the individual maps in turn: + fig = plt.figure(figsize=(7, 8)) gs = gridspec.GridSpec(4, 3, figure=fig) for i, a_map in enumerate(adapt_maps): ax = fig.add_subplot(gs[i], projection=a_map) - a_map.plot(axes=ax, cmap='bwr', vmin=-2, vmax=2, - title=f"Realization {1+i:02d}") - + a_map.plot(axes=ax, cmap='bwr', vmin=-2, vmax=2, title=f"Realization {1+i:02d}") plt.tight_layout(pad=5, h_pad=2) + plt.show() diff --git a/examples/finding_data/plot_hmi.py b/examples/finding_data/plot_hmi.py index e70e8c3..694e37a 100644 --- a/examples/finding_data/plot_hmi.py +++ b/examples/finding_data/plot_hmi.py @@ -24,21 +24,18 @@ from sunpy.net import Fido from sunpy.net import attrs as a -import sunkit_magex.pfss.utils - ############################################################################### # Set up the search. # -# Note that for SunPy versions earlier than 2.0, a time attribute is needed to -# do the search, even if (in this case) it isn't used, as the synoptic maps are -# labelled by Carrington rotation number instead of time -time = a.Time('2010/01/01', '2010/01/01') +# Synoptic maps are labelled by Carrington rotation number instead of time. + series = a.jsoc.Series('hmi.synoptic_mr_polfil_720s') ############################################################################### # Do the search. This will return all the maps in the 'hmi_mrsynop_small_720s # series.' -result = Fido.search(time, series) + +result = Fido.search(series) print(result) ############################################################################### @@ -47,21 +44,21 @@ # notification email. If you use this code, please replace this email address # with your own one, registered here: # http://jsoc.stanford.edu/ajax/register_email.html + crot = a.jsoc.PrimeKey('CAR_ROT', 2210) -result = Fido.search(time, series, crot, - a.jsoc.Notify(os.environ['JSOC_EMAIL'])) +result = Fido.search(series, crot, a.jsoc.Notify(os.environ['JSOC_EMAIL'])) print(result) ############################################################################### # Download the files. This downloads files to the default sunpy download # directory. + files = Fido.fetch(result) print(files) ############################################################################### # Read in a file. This will read in the first file downloaded to a sunpy Map -# object. Note that HMI maps have several bits of metadata that do not comply -# to the FITS standard, so we need to fix them first. +# object. + hmi_map = sunpy.map.Map(files[0]) -sunkit_magex.pfss.utils.fix_hmi_meta(hmi_map) hmi_map.peek() diff --git a/examples/internals/README.rst b/examples/internals/README.rst new file mode 100644 index 0000000..8635553 --- /dev/null +++ b/examples/internals/README.rst @@ -0,0 +1,4 @@ +Internals +--------- + +Provides overview of the internals of each solver. diff --git a/examples/pfsspy_info/plot_computation_grid.py b/examples/internals/plot_computation_grid.py similarity index 86% rename from examples/pfsspy_info/plot_computation_grid.py rename to examples/internals/plot_computation_grid.py index 0492c88..1d7020e 100644 --- a/examples/pfsspy_info/plot_computation_grid.py +++ b/examples/internals/plot_computation_grid.py @@ -12,31 +12,36 @@ import matplotlib.pyplot as plt import numpy as np -from sunkit_magex.pfss.grid import Grid +from sunkit_magex import pfss ############################################################################### -# Define the grid spacings +# Define the grid spacings. + ns = 15 nphi = 360 nr = 10 rss = 2.5 ############################################################################### -# Create the grid -grid = Grid(ns, nphi, nr, rss) +# Create the grid. + +grid = pfss.grid.Grid(ns, nphi, nr, rss) ############################################################################### -# Get the grid edges, and transform to r and theta coordinates +# Get the grid edges, and transform to r and theta coordinates. + r_edges = np.exp(grid.rg) theta_edges = np.arccos(grid.sg) ############################################################################### # The corners of the grid are where lines of constant (r, theta) intersect, # so meshgrid these together to get all the grid corners. + r_grid_points, theta_grid_points = np.meshgrid(r_edges, theta_edges) ############################################################################### -# Plot the resulting grid corners +# Plot the resulting grid corners. + fig = plt.figure() ax = fig.add_subplot(projection='polar') diff --git a/examples/pfsspy_info/tracer_performance.py b/examples/internals/tracer_performance.py similarity index 79% rename from examples/pfsspy_info/tracer_performance.py rename to examples/internals/tracer_performance.py index 1e67434..99cf28c 100644 --- a/examples/pfsspy_info/tracer_performance.py +++ b/examples/internals/tracer_performance.py @@ -1,7 +1,8 @@ """ Tracer performance ================== -Comparing the performance of Python and FORTRAN tracers. + +Comparing the performance of Python and Rust tracers. """ import timeit @@ -11,10 +12,11 @@ import numpy as np import sunpy.map -import sunkit_magex.pfss +from sunkit_magex import pfss ############################################################################### -# Create a dipole map +# Create a dipole map. + ntheta = 180 nphi = 360 nr = 50 @@ -24,29 +26,29 @@ 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, sunkit_magex.pfss.utils.carr_cea_wcs_header('2010-01-01', br.shape)) -pfss_input = sunkit_magex.pfss.Input(br, nr, rss) -pfss_output = sunkit_magex.pfss.pfss(pfss_input) +br = sunpy.map.Map(br.T, pfss.utils.carr_cea_wcs_header('2010-01-01', br.shape)) +pfss_input = pfss.Input(br, nr, rss) +pfss_output = pfss.pfss(pfss_input) print('Computed PFSS solution') ############################################################################### -# Trace some field lines +# Trace some field lines. + seed0 = np.atleast_2d(np.array([1, 1, 0])) -tracers = [sunkit_magex.pfss.tracing.PythonTracer(), - sunkit_magex.pfss.tracing.FortranTracer()] +tracers = [pfss.tracing.PythonTracer(), + pfss.tracing.FortranTracer()] nseeds = 2**np.arange(14) times = [[], []] for nseed in nseeds: print(nseed) seeds = np.repeat(seed0, nseed, axis=0) - r, lat, lon = sunkit_magex.pfss.coords.cart2sph(seeds[:, 0], seeds[:, 1], seeds[:, 2]) + r, lat, lon = pfss.coords.cart2sph(seeds[:, 0], seeds[:, 1], seeds[:, 2]) r = r * astropy.constants.R_sun lat = (lat - np.pi / 2) * u.rad lon = lon * u.rad @@ -60,7 +62,8 @@ def dipole_Br(r, theta): times[i].append(t) ############################################################################### -# Plot the results +# Plot the results. + fig, ax = plt.subplots() ax.scatter(nseeds[1:len(times[0])], times[0][1:], label='python') ax.scatter(nseeds[1:], times[1][1:], label='fortran') @@ -81,6 +84,8 @@ def dipole_Br(r, theta): ax.axvline(180 * 360, color='k', linestyle='--', label='180x360 seed points') ax.legend() + +# Note that the performance is run on a RTD server, so it will change on each run. plt.show() ############################################################################### diff --git a/examples/pfsspy_info/README.rst b/examples/pfsspy_info/README.rst deleted file mode 100644 index 40d54da..0000000 --- a/examples/pfsspy_info/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -Internals ---------- diff --git a/examples/testing/README.rst b/examples/testing/README.rst index e4dd0a0..aaf7db8 100644 --- a/examples/testing/README.rst +++ b/examples/testing/README.rst @@ -1,3 +1,4 @@ Tests ----- -Comparisons of the numerical output of sunkit_magex.pfss to analytic solutions. + +Comparisons of the numerical output of `sunkit_magex.pfss` to analytic solutions. diff --git a/examples/testing/open_flux_harmonics.py b/examples/testing/open_flux_harmonics.py index 3dc3137..b889876 100644 --- a/examples/testing/open_flux_harmonics.py +++ b/examples/testing/open_flux_harmonics.py @@ -1,12 +1,13 @@ """ Open flux comparison (calculations) =================================== + Comparing total unsigned flux to analytic solutions. This script calculates both analytic and numerical values of the total unsigned open flux within PFSS solutions of single spherical harmonics, and saves them to a .json file. This can be read in by ``plot_open_flux_harmonics.py`` to -visualise the result. +visualize the result. """ import json from collections import defaultdict @@ -14,7 +15,8 @@ from helpers import open_flux_analytic, open_flux_numeric, result_dir ############################################################################### -# Set the source surface height, and the (l, m) values to investigate +# Set the source surface height, and the (l, m) values to investigate: + zss = 2 nrho = 40 @@ -34,7 +36,5 @@ flux_numeric = open_flux_numeric(l, m, zss, nrho) results['numeric'][l][m] = float(flux_numeric) -# open file for writing, "w" with open(result_dir / "open_flux_harmonics.json", "w") as f: - # write json object to file f.write(json.dumps(results)) diff --git a/examples/testing/open_flux_nr.py b/examples/testing/open_flux_nr.py index e8056a1..0f42310 100644 --- a/examples/testing/open_flux_nr.py +++ b/examples/testing/open_flux_nr.py @@ -1,6 +1,7 @@ """ -Open flux and radial grid points (calcuations) -============================================== +Open flux and radial grid points (calculations) +=============================================== + Comparing total unsigned flux to analytic solutions. This script caclulates the ratio of numeric to analytic total unsigned open @@ -8,6 +9,7 @@ radial grid cells in the sunkit_magex.pfss grid. """ + import numpy as np import pandas as pd from tqdm import tqdm @@ -15,7 +17,8 @@ from helpers import open_flux_analytic, open_flux_numeric, result_dir ############################################################################### -# Set the source surface height and range of radial grid points +# Set the source surface height and range of radial grid points. + zss = 2 nrhos = np.arange(10, 51, 2) print(f'nrhos={nrhos}') @@ -23,6 +26,7 @@ ############################################################################### # Loop through spherical harmonics and do the calculations. Only the ratio # of fluxes between the analytic and numeric solutions is saved. + df = pd.DataFrame(index=nrhos, columns=[]) for l in range(1, 6): @@ -30,14 +34,12 @@ lm = str(l) + str(m) print(f"l={l}, m={m}") flux_analytic = open_flux_analytic(l, m, zss) - flux_numeric = [] - for nrho in tqdm(nrhos): - flux_numeric.append(open_flux_numeric(l, m, zss, nrho)) + flux_numeric = [open_flux_numeric(l, m, zss, nrho) for nrho in tqdm(nrhos)] flux_numeric = np.array(flux_numeric) flux_ratio = flux_numeric / flux_analytic df[lm] = flux_ratio - ############################################################################### -# Save a copy of the data +# Save a copy of the data. + df.to_csv(result_dir / 'open_flux_results.csv') diff --git a/examples/testing/plot_error_map.py b/examples/testing/plot_error_map.py index be1faa3..9a02f95 100644 --- a/examples/testing/plot_error_map.py +++ b/examples/testing/plot_error_map.py @@ -1,8 +1,9 @@ """ Field line error map ==================== + This script produces a map of errors between analytic field line equations -and field lines numerically traced by sunkit_magex.pfss. +and field lines numerically traced by `sunkit_magex.pfss`. """ import astropy.constants as const import astropy.units as u @@ -11,27 +12,27 @@ from astropy.coordinates import SkyCoord from astropy.visualization import quantity_support -from sunkit_magex.pfss import tracing +from sunkit_magex import pfss from helpers import pffspy_output, phi_fline_coords, theta_fline_coords quantity_support() +############################################################################### +# Calculate PFSS solution. + l = 3 m = 3 nphi = 360 ns = 180 nr = 40 rss = 2 - - -############################################################################### -# Calculate PFSS solution pfsspy_out = pffspy_output(nphi, ns, nr, rss, l, m) - rss = rss * const.R_sun + ############################################################################### -# Trace field lines +# Trace field lines. + n = 90 # Create 1D theta, phi arrays phi = np.linspace(0, 360, n * 2) @@ -47,7 +48,7 @@ dthetas = [] print(f'Tracing {step_size}...') # Trace -tracer = tracing.FortranTracer(step_size=step_size) +tracer = pfss.tracing.FortranTracer(step_size=step_size) flines = tracer.trace(seeds, pfsspy_out) # Set a mask of open field lines mask = flines.connectivities.astype(bool).reshape(theta.shape) diff --git a/examples/testing/plot_harmonic_comparison.py b/examples/testing/plot_harmonic_comparison.py index 0202e38..597b8f5 100644 --- a/examples/testing/plot_harmonic_comparison.py +++ b/examples/testing/plot_harmonic_comparison.py @@ -1,6 +1,7 @@ """ Spherical harmonic comparisons ============================== + Comparing analytical spherical harmonic solutions to PFSS output. """ import matplotlib.pyplot as plt @@ -9,13 +10,13 @@ from helpers import LMAxes, brss_analytic, brss_pfsspy ############################################################################### -# Compare the the sunkit_magex.pfss solution to the analytic solutions. Cuts are taken -# on the source surface at a constant phi value to do a 1D comparison. +# Compare the the `sunkit_magex.pfss` solution to the analytic solutions. +# Cuts are taken on the source surface at a constant phi value to do a 1D comparison. + nphi = 360 ns = 180 rss = 2 nrho = 20 - nl = 2 axs = LMAxes(nl=nl) @@ -37,5 +38,4 @@ ax.set_xlim(0, 180) ax.axhline(0, linestyle='--', linewidth=0.5, color='black') - plt.show() diff --git a/examples/testing/plot_open_flux_harmonics.py b/examples/testing/plot_open_flux_harmonics.py index 3df1647..5cd274a 100644 --- a/examples/testing/plot_open_flux_harmonics.py +++ b/examples/testing/plot_open_flux_harmonics.py @@ -1,11 +1,13 @@ """ Open flux comparison ==================== + Comparing total unsigned flux to analytic solutions. This script plots results generated by ``open_flux_harmonics.py``, comparing analytic and numerical values of the total unsigned open flux within PFSS solutions of single spherical harmonics. + """ import json @@ -35,4 +37,5 @@ ax=axs.all_axs) cbar.ax.set_ylabel(r'$\frac{\Phi_{sunkit_magex.pfss}}{\Phi_{analytic}}$', rotation=0, size=18, labelpad=27, va='center') + plt.show() diff --git a/examples/testing/plot_open_flux_nr.py b/examples/testing/plot_open_flux_nr.py index acc9a98..2c46740 100644 --- a/examples/testing/plot_open_flux_nr.py +++ b/examples/testing/plot_open_flux_nr.py @@ -2,10 +2,10 @@ Open flux and radial grid points ================================ -The script visualises results from ``open_flux_harmonics.py``. +The script visualizes results from ``open_flux_harmonics.py``. It shows the ratio of numeric to analytic total unsigned open fluxes in PFSS solutions of spherical harmonics, as a function of the number of radial grid -cells in the sunkit_magex.pfss grid. +cells in the `sunkit_magex.pfss` grid. """ import matplotlib.pyplot as plt import matplotlib.ticker as mticker diff --git a/examples/testing/plot_tracer_step_size.py b/examples/testing/plot_tracer_step_size.py index 75d41f7..5b6d730 100644 --- a/examples/testing/plot_tracer_step_size.py +++ b/examples/testing/plot_tracer_step_size.py @@ -54,5 +54,4 @@ ax.xaxis.set_ticks([], minor=minor) ax.yaxis.set_ticks([], minor=minor) - plt.show() diff --git a/examples/using_pfsspy/plot_aia_overplotting.py b/examples/using_pfsspy/plot_aia_overplotting.py index 94600e1..4a40cdc 100644 --- a/examples/using_pfsspy/plot_aia_overplotting.py +++ b/examples/using_pfsspy/plot_aia_overplotting.py @@ -5,6 +5,8 @@ This example shows how to take a PFSS solution, trace some field lines, and overplot the traced field lines on an AIA 193 map. """ +# sphinx_gallery_thumbnail_number = 5 + import os import astropy.units as u @@ -18,12 +20,14 @@ from sunkit_magex.pfss.sample_data import get_gong_map ############################################################################### -# Load a GONG magnetic field map +# Load a GONG magnetic field map. + gong_fname = get_gong_map() gong_map = sunpy.map.Map(gong_fname) ############################################################################### -# Load the corresponding AIA 193 map +# Load the corresponding AIA 193 map. + if not os.path.exists('aia_map.fits'): import urllib.request urllib.request.urlretrieve( @@ -37,16 +41,19 @@ # 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 = 25 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 +# surface, we now construct an `Input` object that stores this information. + pfss_in = sunkit_magex.pfss.Input(gong_map, nrho, rss) ############################################################################### -# Using the `Input` object, plot the input photospheric magnetic field +# Using the `Input` object, plot the input photospheric magnetic field. + m = pfss_in.map fig = plt.figure() ax = plt.subplot(projection=m) @@ -58,21 +65,20 @@ # 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) - ############################################################################### -# Now we construct a 5 x 5 grid of footpoitns to trace some magnetic field +# Now we construct a 5 x 5 grid of footpoints to trace some magnetic field # lines from. These coordinates are defined in the helioprojective frame of the -# AIA image +# AIA image. hp_lon = np.linspace(-250, 250, 5) * u.arcsec hp_lat = np.linspace(250, 500, 5) * u.arcsec # Make a 2D grid from these 1D points lon, lat = np.meshgrid(hp_lon, hp_lat) -seeds = SkyCoord(lon.ravel(), lat.ravel(), - frame=aia.coordinate_frame) +seeds = SkyCoord(lon.ravel(), lat.ravel(), frame=aia.coordinate_frame) fig = plt.figure() ax = plt.subplot(projection=aia) aia.plot(axes=ax) @@ -80,7 +86,8 @@ ############################################################################### # Plot the magnetogram and the seed footpoints The footpoints are centered -# around the active region metnioned above. +# around the active region mentioned above. + m = pfss_in.map fig = plt.figure() ax = plt.subplot(projection=m) @@ -96,17 +103,20 @@ ax.set_ylim(bottom=0) ####################################################################### -# Compute the PFSS solution from the GONG magnetic field input +# Compute the PFSS solution from the GONG magnetic field input. + pfss_out = sunkit_magex.pfss.pfss(pfss_in) ############################################################################### # Trace field lines from the footpoints defined above. + tracer = tracing.FortranTracer() flines = tracer.trace(seeds, pfss_out) ############################################################################### -# Plot the input GONG magnetic field map, along with the traced mangetic field +# Plot the input GONG magnetic field map, along with the traced magnetic field # lines. + m = pfss_in.map fig = plt.figure() ax = plt.subplot(projection=m) @@ -116,22 +126,17 @@ for fline in flines: ax.plot_coord(fline.coords, color='black', linewidth=1) -# Set the axes limits. These limits have to be in pixel values -# ax.set_xlim(0, 180) -# ax.set_ylim(45, 135) ax.set_title('Photospheric field and traced field lines') + ############################################################################### # Plot the AIA map, along with the traced magnetic field lines. Inside the # loop the field lines are converted to the AIA observer coordinate frame, # and then plotted on top of the map. + fig = plt.figure() ax = plt.subplot(1, 1, 1, projection=aia) aia.plot(ax) for fline in flines: ax.plot_coord(fline.coords, alpha=0.8, linewidth=1, color='white') -# ax.set_xlim(500, 900) -# ax.set_ylim(400, 800) plt.show() - -# sphinx_gallery_thumbnail_number = 5 diff --git a/examples/using_pfsspy/plot_dipole.py b/examples/using_pfsspy/plot_dipole.py index 53db931..3b19968 100644 --- a/examples/using_pfsspy/plot_dipole.py +++ b/examples/using_pfsspy/plot_dipole.py @@ -2,7 +2,7 @@ Dipole source solution ====================== -A simple example showing how to use sunkit_magex.pfss to compute the solution to a dipole +A simple example showing how to use `pfss` to compute the solution to a dipole source field. """ import astropy.constants as const @@ -14,14 +14,14 @@ from astropy.coordinates import SkyCoord from astropy.time import Time -import sunkit_magex.pfss -import sunkit_magex.pfss.coords as coords +from sunkit_magex import pfss ############################################################################### # To start with we need to construct an input for the PFSS model. To do this, # first set up a regular 2D grid in (phi, s), where s = cos(theta) and # (phi, theta) are the standard spherical coordinate system angular # coordinates. In this case the resolution is (360 x 180). + nphi = 360 ns = 180 @@ -29,32 +29,34 @@ s = np.linspace(-1, 1, ns) s, phi = np.meshgrid(s, phi) - ############################################################################### # Now we can take the grid and calculate the boundary condition magnetic field. + def dipole_Br(r, s): return 2 * s / r**3 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 = sunkit_magex.pfss.utils.carr_cea_wcs_header(Time('2020-1-1'), br.shape) +# surface, we now construct an `pfss.Input` object that stores this information. + +header = pfss.utils.carr_cea_wcs_header(Time('2020-1-1'), br.shape) input_map = sunpy.map.Map((br.T, header)) -pfss_in = sunkit_magex.pfss.Input(input_map, nrho, rss) +pfss_in = pfss.Input(input_map, nrho, rss) ############################################################################### -# Using the Input object, plot the input field +# Using the Input object, plot the input field. + m = pfss_in.map fig = plt.figure() ax = plt.subplot(projection=m) @@ -64,11 +66,12 @@ def dipole_Br(r, s): ############################################################################### # Now calculate the PFSS solution. -pfss_out = sunkit_magex.pfss.pfss(pfss_in) +pfss_out = pfss.pfss(pfss_in) ############################################################################### # Using the Output object we can plot the source surface field, and the # polarity inversion line. + ss_br = pfss_out.source_surface_br # Create the figure and axes @@ -87,6 +90,7 @@ def dipole_Br(r, s): # Finally, using the 3D magnetic field solution we can trace some field lines. # In this case 32 points equally spaced in theta are chosen and traced from # the source surface outwards. + fig, ax = plt.subplots() ax.set_aspect('equal') @@ -96,7 +100,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 = sunkit_magex.pfss.tracing.FortranTracer() +tracer = pfss.tracing.FortranTracer() field_lines = tracer.trace(seeds, pfss_out) for field_line in field_lines: @@ -111,4 +115,5 @@ def dipole_Br(r, s): ax.add_patch(mpatch.Circle((0, 0), pfss_in.grid.rss, color='k', linestyle='--', fill=False)) ax.set_title('PFSS solution for a dipole source field') + plt.show() diff --git a/examples/using_pfsspy/plot_field_line_magnetic_field.py b/examples/using_pfsspy/plot_field_line_magnetic_field.py index 31181d9..2573a5c 100644 --- a/examples/using_pfsspy/plot_field_line_magnetic_field.py +++ b/examples/using_pfsspy/plot_field_line_magnetic_field.py @@ -11,33 +11,34 @@ import sunpy.map from astropy.coordinates import SkyCoord -import sunkit_magex.pfss -from sunkit_magex.pfss import tracing -from sunkit_magex.pfss.sample_data import get_gong_map +from sunkit_magex import pfss ############################################################################### -# Load a GONG magnetic field map -gong_fname = get_gong_map() +# Load a GONG magnetic field map. + +gong_fname = pfss.sample_data.get_gong_map() gong_map = sunpy.map.Map(gong_fname) ############################################################################### # 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 -pfss_in = sunkit_magex.pfss.Input(gong_map, nrho, rss) -pfss_out = sunkit_magex.pfss.pfss(pfss_in) + +pfss_in = pfss.Input(gong_map, nrho, rss) +pfss_out = pfss.pfss(pfss_in) ############################################################################### # Now take a seed point, and trace a magnetic field line through the PFSS # solution from this point -tracer = tracing.FortranTracer() +tracer = pfss.tracing.FortranTracer() r = 1.2 * const.R_sun lat = 70 * u.deg lon = 0 * u.deg @@ -52,6 +53,7 @@ # # From the plot we can see that the non-radial component of the mangetic field # goes to zero at the source surface, as expected. + field_line = field_lines[0] B = field_line.b_along_fline r = field_line.coords.radius diff --git a/examples/using_pfsspy/plot_gong.py b/examples/using_pfsspy/plot_gong.py index ee6b059..4ba9dd1 100644 --- a/examples/using_pfsspy/plot_gong.py +++ b/examples/using_pfsspy/plot_gong.py @@ -4,6 +4,8 @@ Calculating PFSS solution for a GONG synoptic magnetic field map. """ +# sphinx_gallery_thumbnail_number = 4 + import astropy.constants as const import astropy.units as u import matplotlib.pyplot as plt @@ -11,35 +13,37 @@ import sunpy.map from astropy.coordinates import SkyCoord -import sunkit_magex.pfss -from sunkit_magex.pfss import coords, tracing -from sunkit_magex.pfss.sample_data import get_gong_map +from sunkit_magex import pfss ############################################################################### -# Load a GONG magnetic field map -gong_fname = get_gong_map() +# Load a GONG magnetic field map. + +gong_fname = pfss.sample_data.get_gong_map() gong_map = sunpy.map.Map(gong_fname) ############################################################################### # 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 -pfss_in = sunkit_magex.pfss.Input(gong_map, nrho, rss) +# surface, we now construct an Input object that stores this information. +pfss_in = pfss.Input(gong_map, nrho, rss) + + +############################################################################### +# Using the Input object, plot the input field. def set_axes_lims(ax): ax.set_xlim(0, 360) ax.set_ylim(0, 180) -############################################################################### -# Using the Input object, plot the input field m = pfss_in.map fig = plt.figure() ax = plt.subplot(projection=m) @@ -49,12 +53,14 @@ def set_axes_lims(ax): set_axes_lims(ax) ############################################################################### -# Now calculate the PFSS solution -pfss_out = sunkit_magex.pfss.pfss(pfss_in) +# Now calculate the PFSS solution. + +pfss_out = pfss.pfss(pfss_in) ############################################################################### # Using the Output object we can plot the source surface field, and the # polarity inversion line. + ss_br = pfss_out.source_surface_br # Create the figure and axes fig = plt.figure() @@ -92,15 +98,15 @@ def set_axes_lims(ax): ax.set_title('$B_{r}$ ' + f'at r={r:.2f}' + '$r_{\\odot}$') set_axes_lims(ax) - ############################################################################### # Finally, using the 3D magnetic field solution we can trace some field lines. # In this case 64 points equally gridded in theta and phi are chosen and # traced from the source surface outwards. + fig = plt.figure() ax = fig.add_subplot(111, projection='3d') -tracer = tracing.FortranTracer() +tracer = pfss.tracing.FortranTracer() 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) @@ -121,6 +127,5 @@ def set_axes_lims(ax): color=color, linewidth=1) ax.set_title('PFSS solution') -plt.show() -# sphinx_gallery_thumbnail_number = 4 +plt.show() diff --git a/examples/using_pfsspy/plot_hmi.py b/examples/using_pfsspy/plot_hmi.py index 3a147d4..2451fe4 100644 --- a/examples/using_pfsspy/plot_hmi.py +++ b/examples/using_pfsspy/plot_hmi.py @@ -1,6 +1,7 @@ """ HMI PFSS solutions ------------------- +================== + Calculating a PFSS solution from a HMI synoptic map. This example shows how to calcualte a PFSS solution from a HMI synoptic map. @@ -18,16 +19,13 @@ from sunpy.net import Fido from sunpy.net import attrs as a -import sunkit_magex.pfss -import sunkit_magex.pfss.utils +from sunkit_magex import pfss ############################################################################### # Set up the search. # -# Note that for SunPy versions earlier than 2.0, a time attribute is needed to -# do the search, even if (in this case) it isn't used, as the synoptic maps are -# labelled by Carrington rotation number instead of time -time = a.Time('2010/01/01', '2010/01/01') +# The synoptic maps are labelled by Carrington rotation number instead of time. + series = a.jsoc.Series('hmi.synoptic_mr_polfil_720s') crot = a.jsoc.PrimeKey('CAR_ROT', 2210) @@ -37,32 +35,36 @@ # If you use this code, please replace this email address # with your own one, registered here: # http://jsoc.stanford.edu/ajax/register_email.html -result = Fido.search(time, series, crot, - a.jsoc.Notify(os.environ["JSOC_EMAIL"])) + +result = Fido.search(series, crot, a.jsoc.Notify(os.environ["JSOC_EMAIL"])) files = Fido.fetch(result) ############################################################################### # Read in a file. This will read in the first file downloaded to a sunpy Map -# object +# object. + hmi_map = sunpy.map.Map(files[0]) 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 -pfss_in = sunkit_magex.pfss.Input(hmi_map, nrho, rss) -pfss_out = sunkit_magex.pfss.pfss(pfss_in) +pfss_in = pfss.Input(hmi_map, nrho, rss) +pfss_out = pfss.pfss(pfss_in) ############################################################################### # Using the Output object we can plot the source surface field, and the # polarity inversion line. + ss_br = pfss_out.source_surface_br # Create the figure and axes fig = plt.figure() diff --git a/examples/using_pfsspy/plot_open_closed_map.py b/examples/using_pfsspy/plot_open_closed_map.py index 5dd00b2..5a03b90 100644 --- a/examples/using_pfsspy/plot_open_closed_map.py +++ b/examples/using_pfsspy/plot_open_closed_map.py @@ -12,32 +12,32 @@ import sunpy.map from astropy.coordinates import SkyCoord -import sunkit_magex.pfss -from sunkit_magex.pfss import tracing -from sunkit_magex.pfss.sample_data import get_gong_map +from sunkit_magex import pfss ############################################################################### -# Load a GONG magnetic field map -gong_fname = get_gong_map() +# Load a GONG magnetic field map. + +gong_fname = pfss.sample_data.get_gong_map() gong_map = sunpy.map.Map(gong_fname) ############################################################################### -# Set the model parameters +# Set the model parameters. + nrho = 40 rss = 2.5 ############################################################################### -# Construct the input, and calculate the output solution -pfss_in = sunkit_magex.pfss.Input(gong_map, nrho, rss) -pfss_out = sunkit_magex.pfss.pfss(pfss_in) +# Construct the input, and calculate the output solution. +pfss_in = pfss.Input(gong_map, nrho, rss) +pfss_out = pfss.pfss(pfss_in) ############################################################################### # Finally, using the 3D magnetic field solution we can trace some field lines. # In this case a grid of 90 x 180 points equally gridded in theta and phi are # chosen and traced from the source surface outwards. # -# First, set up the tracing seeds +# First, set up the tracing seeds. r = const.R_sun # Number of steps in cos(latitude) @@ -49,9 +49,10 @@ seeds = SkyCoord(lon.ravel(), lat.ravel(), r, frame=pfss_out.coordinate_frame) ############################################################################### -# Trace the field lines +# Trace the field lines. + print('Tracing field lines...') -tracer = tracing.FortranTracer(max_steps=2000) +tracer = pfss.tracing.FortranTracer(max_steps=2000) field_lines = tracer.trace(seeds, pfss_out) print('Finished tracing field lines') @@ -59,6 +60,7 @@ # Plot the result. The to plot is the input magnetogram, and the bottom plot # shows a contour map of the the footpoint polarities, which are +/- 1 for open # field regions and 0 for closed field regions. + fig = plt.figure() m = pfss_in.map ax = fig.add_subplot(2, 1, 1, projection=m) diff --git a/examples/utils/plot_car_reproject.py b/examples/utils/plot_car_reproject.py index b7ab0a7..d7139e9 100644 --- a/examples/utils/plot_car_reproject.py +++ b/examples/utils/plot_car_reproject.py @@ -1,30 +1,33 @@ """ Re-projecting from CAR to CEA ------------------------------ +============================= -The sunkit_magex.pfss solver takes a cylindrical-equal-area (CEA) projected magnetic field +The `sunkit_magex.pfss` solver takes a cylindrical-equal-area (CEA) projected magnetic field map as input, which is equally spaced in sin(latitude). Some synoptic field maps are equally spaced in latitude, a plate carée (CAR) projection, and need reprojecting. This example shows how to use the `sunkit_magex.pfss.utils.car_to_cea` function to -reproject a CAR projection to a CEA projection that sunkit_magex.pfss can take as input. +reproject a CAR projection to a CEA projection that `sunkit_magex.pfss` can take as input. """ import matplotlib.pyplot as plt -from sunkit_magex.pfss import sample_data, utils +from sunkit_magex import pfss ############################################################################### -# Load a sample ADAPT map, which has a CAR projection -adapt_maps = utils.load_adapt(sample_data.get_adapt_map()) +# 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] ############################################################################### -# Re-project into a CEA projection -adapt_map_cea = utils.car_to_cea(adapt_map_car) +# Re-project into a CEA projection. + +adapt_map_cea = pfss.utils.car_to_cea(adapt_map_car) ############################################################################### -# Plot the original map and the reprojected map +# Plot the original map and the reprojected map. + plt.figure() adapt_map_car.plot() plt.figure() diff --git a/pyproject.toml b/pyproject.toml index 97fe093..db17aa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,46 +7,42 @@ build-backend = "setuptools.build_meta" [project] name = "sunkit_magex" -description = "Potential field source surface modelling" -readme = "README.rst" -requires-python = ">=3.9" -license = { file = "licenses/LICENSE.rst", content-type = "text/plain" } +description = "Solar Magnetic field Extrapollation" +requires-python = ">=3.10" +license = { file = "licenses/LICENSE.rst"} authors = [ { name = "The SunPy Community", email = "sunpy@googlegroups.com" }, ] dependencies = [ - "astropy>=5.0", - "numpy", - "scikit-image", - "scipy", - "sunpy[map]>=4,<5.1", + "astropy>=5.2.0", + "numpy>=1.21.0", + "scikit-image>=0.19.0", + # !=1.10.0 due to https://github.com/scipy/scipy/issues/17718 + "scipy>=1.8.0,!=1.10.0", + "sunpy[map]>=5.1", ] dynamic = ["version"] [project.optional-dependencies] tests = [ - "pytest", - "pytest-doctestplus", - "pytest-cov", - "parfive", - "reproject", - "streamtracer", - "sympy", + "pytest", + "pytest-doctestplus", + "pytest-cov", + "parfive", + "reproject", + "streamtracer", + "sympy", ] docs = [ - "sphinx", - "sphinx-automodapi", - "tomli; python_version <\"3.11\"", - "graphviz", - "importlib_metadata", - "pillow", - "reproject", - "sphinx-gallery", - "sphinx_rtd_theme", - "streamtracer", - "sunpy[net,map]", - "sympy", - "sunpy-sphinx-theme", + "sphinx", + "sphinx-automodapi", + "pillow", + "reproject", + "sphinx-gallery", + "streamtracer", + "sunpy[net,map]", + "sympy", + "sunpy-sphinx-theme", ] performance = [ "numba", @@ -56,8 +52,17 @@ analytic = [ "sympy", ] +[project.readme] +file = "README.rst" +content-type = "text/x-rst" + [project.urls] -repository = "https://sunpy.org" +Homepage = "https://sunpy.org" +Download = "https://pypi.org/project/sunkit-magex/" +"Source Code" = "https://github.com/sunpy/sunkit-magex/" +Documentation = "https://docs.sunpy.org/" +Changelog = "https://docs.sunpy.org/en/stable/whatsnew/changelog.html" +"Issue Tracker" = "https://github.com/sunpy/sunkit-magex/issues" [tool.setuptools] zip-safe = false @@ -79,20 +84,20 @@ addopts = "--doctest-rst" [tool.coverage.run] omit = [ - "sunkit_magex/__init*", - "sunkit_magex/conftest.py", - "sunkit_magex/*setup_package*", - "sunkit_magex/tests/*", - "sunkit_magex/*/tests/*", - "sunkit_magex/extern/*", - "sunkit_magex/version*", - "*/sunkit_magex/__init*", - "*/sunkit_magex/conftest.py", - "*/sunkit_magex/*setup_package*", - "*/sunkit_magex/tests/*", - "*/sunkit_magex/*/tests/*", - "*/sunkit_magex/extern/*", - "*/sunkit_magex/version*", + "sunkit_magex/__init*", + "sunkit_magex/conftest.py", + "sunkit_magex/*setup_package*", + "sunkit_magex/tests/*", + "sunkit_magex/*/tests/*", + "sunkit_magex/extern/*", + "sunkit_magex/version*", + "*/sunkit_magex/__init*", + "*/sunkit_magex/conftest.py", + "*/sunkit_magex/*setup_package*", + "*/sunkit_magex/tests/*", + "*/sunkit_magex/*/tests/*", + "*/sunkit_magex/extern/*", + "*/sunkit_magex/version*", ] [tool.coverage.report] diff --git a/sunkit_magex/__init__.py b/sunkit_magex/__init__.py index 2fe8174..20b38d5 100644 --- a/sunkit_magex/__init__.py +++ b/sunkit_magex/__init__.py @@ -1,2 +1,3 @@ from .version import version as __version__ + __all__ = [] diff --git a/sunkit_magex/data/README.rst b/sunkit_magex/data/README.rst deleted file mode 100644 index 382f6e7..0000000 --- a/sunkit_magex/data/README.rst +++ /dev/null @@ -1,6 +0,0 @@ -Data directory -============== - -This directory contains data files included with the package source -code distribution. Note that this is intended only for relatively small files -- large files should be externally hosted and downloaded as needed. diff --git a/sunkit_magex/pfss/__init__.py b/sunkit_magex/pfss/__init__.py index 8c48923..942d06d 100644 --- a/sunkit_magex/pfss/__init__.py +++ b/sunkit_magex/pfss/__init__.py @@ -1,19 +1,18 @@ -# Import sunkit_magex.pfss sub-modules to have them available through sunkit_magex.pfss.{name} -try: - import sunkit_magex.pfss.analytic -except ModuleNotFoundError: - # If sympy isn't installed - pass -import sunkit_magex.pfss.coords -import sunkit_magex.pfss.fieldline # Import this to register map sources -import sunkit_magex.pfss.map -import sunkit_magex.pfss.sample_data -import sunkit_magex.pfss.tracing -import sunkit_magex.pfss.utils +import sunkit_magex.pfss.map as _ +from sunkit_magex.pfss import coords +from sunkit_magex.pfss import fieldline +from sunkit_magex.pfss import sample_data +from sunkit_magex.pfss import tracing +from sunkit_magex.pfss import utils +from sunkit_magex.pfss.input import Input +from sunkit_magex.pfss.output import Output +from sunkit_magex.pfss.pfss import pfss -from .input import Input -from .output import Output -from .pfss import pfss +__all__ = ['coords', 'fieldline', 'sample_data', 'tracing', 'utils', 'Input', 'Output', 'pfss'] -__all__ = ['Input', 'Output', 'pfss'] +try: + from sunkit_magex.pfss import analytic + __all__.append('analytic') +except ImportError: + pass diff --git a/sunkit_magex/pfss/analytic.py b/sunkit_magex/pfss/analytic.py index 947a150..83d7080 100644 --- a/sunkit_magex/pfss/analytic.py +++ b/sunkit_magex/pfss/analytic.py @@ -34,14 +34,14 @@ def _normalise_angles(theta: u.deg, phi: u.deg): return theta, phi -def _Ynm(l, m, theta, phi): +def _Ynm(l, m, theta, phi): # NOQA: E741 """ Return values of spherical harmonic with numbers l, m at coordiantes theta, phi. """ # Note swapped arguments phi, theta, as scipy has a different # definition of these - return -scipy.special.sph_harm(m, l, phi, theta) + return -scipy.special.sph_harm(m, l, phi, theta) # NOQA: E741 def _cot(theta): @@ -51,7 +51,7 @@ def _cot(theta): _extras = {'Ynm': _Ynm, 'cot': _cot, 'exp': np.exp} -def _spherical_harmonic_sympy(l, m): +def _spherical_harmonic_sympy(l, m): # NOQA: E741 """ Return a complex spherical harmonic with numbers ``l, m``. @@ -79,7 +79,7 @@ def _spherical_harmonic_sympy(l, m): return harm, theta, phi -def _c(l, zss): +def _c(l, zss): # NOQA: E741 """ """ def cl(z): @@ -90,7 +90,7 @@ def cl(z): return cl -def _d(l, zss): +def _d(l, zss): # NOQA: E741 """ """ def dl(z): @@ -101,7 +101,7 @@ def dl(z): return dl -def Br(l, m, zss): +def Br(l, m, zss): # NOQA: E741 """ Analytic radial component of magnetic field on the source surface. @@ -128,7 +128,7 @@ def f(z, theta: u.deg, phi: u.deg): return f -def Btheta(l, m, zss): +def Btheta(l, m, zss): # NOQA: E741 """ Analytic theta component of magnetic field on the source surface. @@ -156,7 +156,7 @@ def f(z, theta: u.deg, phi: u.deg): return f -def Bphi(l, m, zss): +def Bphi(l, m, zss): # NOQA: E741 """ Analytic phi component of magnetic field on the source surface. diff --git a/sunkit_magex/pfss/interpolator.py b/sunkit_magex/pfss/interpolator.py index c9748c0..e4982d7 100644 --- a/sunkit_magex/pfss/interpolator.py +++ b/sunkit_magex/pfss/interpolator.py @@ -4,8 +4,10 @@ This module contains a version of scipy.interpolate.RegularGridInterpolator, which has been edited for performance. -THE CODE HERE IS LIABLE TO CHANGE/BREAK AT ANY TIME. Do not use this code -outside of sunkit_magex.pfss. +.. warning:: + + THE CODE HERE IS LIABLE TO CHANGE/BREAK AT ANY TIME. + Do not use this code outside of sunkit_magex.pfss. """ import itertools diff --git a/sunkit_magex/pfss/map.py b/sunkit_magex/pfss/map.py index c00b9d8..89a8a59 100644 --- a/sunkit_magex/pfss/map.py +++ b/sunkit_magex/pfss/map.py @@ -2,43 +2,9 @@ Custom `sunpy.map.GenericMap` sub-classes for different magnetogram sources. """ import astropy.units as u -import numpy as np import sunpy.map -from astropy.time import Time -__all__ = ['GongSynopticMap', 'ADAPTMap'] - - -class GongSynopticMap(sunpy.map.GenericMap): - def __init__(self, data, header, **kwargs): - # Fix coordinate system stuff - if 'KEYCOMMENTS' in header: - if 'deg' in header['KEYCOMMENTS']['CDELT1']: - header['CUNIT1'] = 'deg' - if header['KEYCOMMENTS']['CDELT2'] == 'Sine-lat step': - header['CUNIT2'] = 'deg' - # Instead of the spacing in sin(lat), this should be 180/pi times - # that value (see Thompson 2005) - header['CDELT2'] = 180 / np.pi * header['CDELT2'] - header['KEYCOMMENTS']['CDELT2'] = '180 / pi * sine-lat step' - # Fix timestamp - if 'time-obs' in header: - header['date-obs'] = (header['date-obs'] + ' ' + - header.pop('time-obs')) - header['date-obs'] = Time(header['date-obs']).isot - # Fix unit - if 'bunit' in header and header['bunit'] == 'Gauss': - header['bunit'] = 'G' - # Fix observer coordinate - if 'hglt_obs' not in header: - header.update(_earth_obs_coord_meta(header['date-obs'])) - super().__init__(data, header, **kwargs) - - @classmethod - def is_datasource_for(cls, data, header, **kwargs): - """Determines if header corresponds to an GONG map.""" - return (str(header.get('TELESCOP', '')).endswith('GONG') and - str(header.get('CTYPE1', '').startswith('CRLN'))) +__all__ = ['ADAPTMap'] class ADAPTMap(sunpy.map.GenericMap): @@ -67,8 +33,7 @@ def _observer_coord_meta(observer_coord): obstime=observer_coord.obstime) observer_coord = observer_coord.transform_to(new_obs_frame) - new_meta = {} - new_meta['hglt_obs'] = observer_coord.lat.to_value(u.deg) + 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 diff --git a/sunkit_magex/pfss/pfss.py b/sunkit_magex/pfss/pfss.py index 7506a0d..d0efffc 100644 --- a/sunkit_magex/pfss/pfss.py +++ b/sunkit_magex/pfss/pfss.py @@ -1,16 +1,16 @@ """ Code for calculating a PFSS extrapolation. """ + +import contextlib import numpy as np import sunkit_magex.pfss HAS_NUMBA = False -try: +with contextlib.suppress(Exception): import numba HAS_NUMBA = True -except Exception: - pass def _eigh(A): @@ -18,7 +18,7 @@ def _eigh(A): def _compute_r_term(m, k, ns, Q, brt, lam, ffm, nr, ffp, psi, psir): - for l in range(ns): + for l in range(ns): # NOQA: E741 # Ignore the l=0 and m=0 term; for a globally divergence free field # this term is zero anyway, but numerically it may be small which # causes numerical issues when solving for c, d diff --git a/sunkit_magex/pfss/tests/example_maps.py b/sunkit_magex/pfss/tests/conftest.py similarity index 100% rename from sunkit_magex/pfss/tests/example_maps.py rename to sunkit_magex/pfss/tests/conftest.py diff --git a/sunkit_magex/pfss/tests/test_analytic.py b/sunkit_magex/pfss/tests/test_analytic.py index e7101ab..39332f3 100644 --- a/sunkit_magex/pfss/tests/test_analytic.py +++ b/sunkit_magex/pfss/tests/test_analytic.py @@ -18,7 +18,7 @@ def test_bangular_rss(f): def test_br_rss(): zss = 2 - l = 1 + l = 1 # NOQA: E741 m = 0 c = zss**(-l-2) * ((2*l + 1) / (l + 1 + l * zss**(-2*l - 1))) f = analytic.Br(l, m, zss) diff --git a/sunkit_magex/pfss/tests/test_fieldline.py b/sunkit_magex/pfss/tests/test_fieldline.py index c4fb18f..dab8fa8 100644 --- a/sunkit_magex/pfss/tests/test_fieldline.py +++ b/sunkit_magex/pfss/tests/test_fieldline.py @@ -9,9 +9,9 @@ @pytest.mark.parametrize(("x", "open", "pol"), - [[[1, 2.5], True, 1], - [[2.5, 1], True, -1], - [[1, 1], False, 0], + [([1, 2.5], True, 1), + ([2.5, 1], True, -1), + ([1, 1], False, 0), ]) def test_open(x, open, pol): fline = FieldLine(x, [0, 0], [0, 0], None) @@ -27,10 +27,10 @@ def test_open(x, open, pol): @pytest.mark.parametrize(("x", "cls"), - [[[1, 2.5], ClosedFieldLines], - [[1, 1], OpenFieldLines], + [([1, 2.5], ClosedFieldLines), + ([1, 1], OpenFieldLines), ]) def test_flines_errors(x, cls): fline = FieldLine(x, [0, 0], [0, 0], None) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="goose"): cls([fline]) diff --git a/sunkit_magex/pfss/tests/test_map.py b/sunkit_magex/pfss/tests/test_map.py index d6ebc10..263cce4 100644 --- a/sunkit_magex/pfss/tests/test_map.py +++ b/sunkit_magex/pfss/tests/test_map.py @@ -4,9 +4,6 @@ import sunkit_magex.pfss.map -from .example_maps import adapt_map, gong_map # NoQA - - def test_gong_source(gong_map): m = sunpy.map.Map(gong_map) assert isinstance(m, sunkit_magex.pfss.map.GongSynopticMap) diff --git a/sunkit_magex/pfss/tests/test_pfss.py b/sunkit_magex/pfss/tests/test_pfss.py index 8f6ed5d..a4c6ab5 100644 --- a/sunkit_magex/pfss/tests/test_pfss.py +++ b/sunkit_magex/pfss/tests/test_pfss.py @@ -15,8 +15,6 @@ import sunkit_magex.pfss.coords from sunkit_magex.pfss import tracing -from .example_maps import dipole_map, dipole_result, gong_map, zero_map # NoQA - R_sun = const.R_sun test_data = pathlib.Path(__file__).parent / 'data' diff --git a/sunkit_magex/pfss/tests/test_tracers.py b/sunkit_magex/pfss/tests/test_tracers.py index cf56764..b3a77e1 100644 --- a/sunkit_magex/pfss/tests/test_tracers.py +++ b/sunkit_magex/pfss/tests/test_tracers.py @@ -7,21 +7,16 @@ import sunkit_magex.pfss from sunkit_magex.pfss import tracing -from .example_maps import dipole_map, dipole_result # NoQA - - @pytest.fixture(params=[tracing.PythonTracer(), tracing.FortranTracer()], ids=['python', 'fortran']) def flines(dipole_result, request): tracer = request.param - input, out = dipole_result + _, out = dipole_result out_frame = out.coordinate_frame seed = coord.SkyCoord(2*u.deg, -45*u.deg, 1.01*const.R_sun, frame=out_frame) - flines = tracer.trace(seed, out) - print(flines[0].coords) - return flines + return tracer.trace(seed, out) def test_field_lines(flines): diff --git a/sunkit_magex/pfss/tests/test_utils.py b/sunkit_magex/pfss/tests/test_utils.py index b6b8bb9..0867ab0 100644 --- a/sunkit_magex/pfss/tests/test_utils.py +++ b/sunkit_magex/pfss/tests/test_utils.py @@ -8,9 +8,6 @@ import sunkit_magex.pfss from sunkit_magex.pfss import utils -from .example_maps import adapt_map, dipole_map, gong_map # NoQA - - def test_load_adapt(adapt_map): adaptMapSequence = utils.load_adapt(adapt_map) assert isinstance(adaptMapSequence, sunpy.map.MapSequence) @@ -63,7 +60,7 @@ def test_validation(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) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match='moose'): utils.is_full_sun_synoptic_map(dipole_map, error=True) @@ -82,7 +79,7 @@ def test_car_reproject(adapt_map): utils.car_to_cea(adapt_map, method='gibberish') -def test_roll_map(gong_map): +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, diff --git a/sunkit_magex/pfss/tracing.py b/sunkit_magex/pfss/tracing.py index e5376a3..0b1e021 100644 --- a/sunkit_magex/pfss/tracing.py +++ b/sunkit_magex/pfss/tracing.py @@ -139,9 +139,9 @@ def vector_grid(output): # (theta direction becomes singular at the poles so it is not cyclic) cyclic = [True, False, False] origin_coord = [0, -1, 0] - vector_grid = VectorGrid(vectors, grid_spacing, cyclic=cyclic, - origin_coord=origin_coord) - return vector_grid + return VectorGrid( + vectors, grid_spacing, cyclic=cyclic, origin_coord=origin_coord + ) def trace(self, seeds, output): if self.max_steps == 'auto': diff --git a/sunkit_magex/pfss/utils.py b/sunkit_magex/pfss/utils.py index 30ea00d..c056a7a 100644 --- a/sunkit_magex/pfss/utils.py +++ b/sunkit_magex/pfss/utils.py @@ -10,51 +10,8 @@ from astropy import units as u from astropy.wcs import WCS -import sunkit_magex.pfss.map - - -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(sunkit_magex.pfss.map._earth_obs_coord_meta( - hmi_map.meta['date-obs'])) +__all__ = ['load_adapt', 'carr_cea_wcs_header', 'is_cea_map', 'is_car_map'] def load_adapt(adapt_path): """ @@ -74,18 +31,14 @@ def load_adapt(adapt_path): Returns ------- - adaptMapSequence : `sunpy.map.MapSequence` + `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] - adaptMapSequence = sunpy.map.Map(data_header_pairs, sequence=True) - return adaptMapSequence + 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) @u.quantity_input @@ -96,7 +49,7 @@ def carr_cea_wcs_header(dtime, shape, *, map_center_longitude=0*u.deg): Parameters ---------- - dtime : datetime, None + dtime : datetime Datetime to associate with the map. shape : tuple Map shape. The first entry should be number of points in longitude, the @@ -111,12 +64,8 @@ def carr_cea_wcs_header(dtime, shape, *, map_center_longitude=0*u.deg): .. [1] W. T. Thompson, "Coordinate systems for solar image data", https://doi.org/10.1051/0004-6361:20054262 """ - # If datetime is None, put in a dummy value here to make - # make_fitswcs_header happy, then strip it out at the end - obstime = dtime or astropy.time.Time('2000-1-1') - frame_out = coord.SkyCoord( - map_center_longitude, 0 * u.deg, const.R_sun, obstime=obstime, + map_center_longitude, 0 * u.deg, const.R_sun, obstime=dtime, frame="heliographic_carrington", observer='self') # Construct header header = sunpy.map.make_fitswcs_header( @@ -126,7 +75,6 @@ def carr_cea_wcs_header(dtime, shape, *, map_center_longitude=0*u.deg): reference_pixel=[(shape[0] / 2) - 0.5, (shape[1] / 2) - 0.5] * u.pix, projection_code="CEA") - # Fix CDELT for lat axis header['CDELT2'] = (180 / np.pi) * (2 / shape[1]) # pop out the time if it isn't supplied @@ -144,8 +92,7 @@ def _check_projection(m, proj_code, error=False): proj = _get_projection(m, i) if proj != proj_code: if error: - raise ValueError(f'Projection type in CTYPE{i} keyword ' - f'must be {proj_code} (got "{proj}")') + raise ValueError(f'Projection type in CTYPE{i} keyword must be {proj_code} (got "{proj}")') return False return True @@ -187,14 +134,13 @@ def is_full_sun_synoptic_map(m, error=False): If `True`, raise an error if *m* does not span the whole solar surface. """ projection = _get_projection(m, 1) - checks = {'CEA': _is_full_sun_cea, - 'CAR': _is_full_sun_car} - if projection not in checks.keys(): - raise NotImplementedError('is_full_sun_synoptic_map is only ' - 'implemented for ' - f'{[key for key in checks.keys()]} ' - 'projections.') - return checks[projection](m, error) + checks = {'CEA': _is_full_sun_cea, 'CAR': _is_full_sun_car} + if projection in checks: + return checks[projection](m, error) + else: + raise NotImplementedError( + f'is_full_sun_synoptic_map is only implemented for {list(checks.keys())} projections.' + ) def _is_full_sun_car(m, error=False): @@ -249,10 +195,10 @@ def car_to_cea(m, method='interp'): """ Reproject a plate-carée map in to a cylindrical-equal-area map. - The solver used in sunkit_magex.pfss requires a magnetic field map with values + The solver used in `sunkit_magex.pfss` requires a magnetic field map with values equally spaced in sin(lat) (ie. a CEA projection), but some maps are provided equally spaced in lat (ie. a CAR projection). This function - reprojects a CAR map into a CEA map so it can be used with sunkit_magex.pfss. + reprojects a CAR map into a CEA map so it can be used with `sunkit_magex.pfss`. Parameters ---------- diff --git a/sunkit_magex/tests/__init__.py b/sunkit_magex/tests/__init__.py deleted file mode 100644 index 838b457..0000000 --- a/sunkit_magex/tests/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Licensed under a 3-clause BSD style license - see LICENSE.rst -""" -This module contains package tests. -""" diff --git a/tox.ini b/tox.ini index c26d7f8..2d64527 100644 --- a/tox.ini +++ b/tox.ini @@ -3,22 +3,21 @@ min_version = 4.0 requires = tox-pypi-filter>=0.14 envlist = - py{39,310,311,312} + py{310,311,312} py312-devdeps - py39-oldestdeps + py310-oldestdeps codestyle build_docs [testenv] pypi_filter = https://raw.githubusercontent.com/sunpy/sunpy/main/.test_package_pins.txt -# Run the tests in a temporary directory to make sure that we don't import -# the package from the source tree +# Run the tests in a temporary directory to make sure that we don't import the package from the source tree +# This is only needed if you do not use a src directory like a sane person change_dir = .tmp/{envname} description = run tests oldestdeps: with the oldest supported version of key dependencies devdeps: with the latest developer version of key dependencies - pass_env = # Custom compiler locations (such as ccache) CC @@ -26,7 +25,6 @@ pass_env = LOCALE_ARCHIVE # If the user has set a LC override we should follow it LC_ALL - set_env = MPLBACKEND=agg devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple @@ -40,22 +38,13 @@ deps = # devdeps: git+https://github.com/ndcube/ndcube oldestdeps: minimum_dependencies pytest-xdist - -# The following indicates which extras_require will be installed extras = tests - commands_pre = oldestdeps: minimum_dependencies sunkit_magex --filename requirements-min.txt oldestdeps: pip install -r requirements-min.txt pip freeze --all --no-input - commands = - # To run different commands for different factors exclude the factor from the default command like this - # !online: {env:PYTEST_COMMAND} {posargs} - # Then specify a specific one like this - # online: {env:PYTEST_COMMAND} --remote-data=any {posargs} - # If you have no factors which require different commands this is all you need: {env:PYTEST_COMMAND} {posargs} [testenv:codestyle]