Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sm2mlt #610

Merged
merged 2 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyspedas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
from .cotrans.cotrans_get_coord import cotrans_get_coord
from .cotrans.cotrans_set_coord import cotrans_set_coord
from .cotrans.tvector_rotate import tvector_rotate
from .cotrans.cart2spc import cart2spc
from .cotrans.spc2cart import spc2cart
from .cotrans.sm2mlt import sm2mlt

from .mms import mms_load_mec, mms_load_fgm, mms_load_scm, mms_load_edi, \
mms_load_edp, mms_load_eis, mms_load_feeps, \
Expand Down
51 changes: 51 additions & 0 deletions pyspedas/cotrans/cart2spc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Convert Cartesian coordinates to spherical coordinates.

This function is similar to cart2spc.pro in IDL SPEDAS.
"""

import numpy as np


def cart2spc(x, y, z):
"""
Convert Cartesian coordinates to spherical coordinates.

Parameters:
-----------
x : float
X coordinate in Cartesian system.
y : float
Y coordinate in Cartesian system.
z : float
Z coordinate in Cartesian system.

Returns:
--------
r : float
Radial distance from the origin.
theta : float
Polar angle (angle from the positive z-axis to the point) in radians. It ranges from [0, pi].
phi : float
Azimuthal angle (angle in the xy-plane from the positive x-axis to the point) in radians. It ranges from [0, 2*pi].

Notes:
------
- Uses the following equations for conversion:
r = sqrt(x^2 + y^2 + z^2)
theta = arccos(z/r)
phi = 2*pi - arccos(x/sqrt(x^2 + y^2)) if y < 0, otherwise arccos(x/sqrt(x^2 + y^2))
"""

x, y, z = np.array(x), np.array(y), np.array(z)
r = np.sqrt(x**2 + y**2 + z**2)

# Calculate phi
maskn = np.where(y < 0, 1, 0)
maskp = np.where(y > 0, 1, 0)
phi = 2*np.pi - maskn * np.arccos(x/np.sqrt(x**2 + y**2)) + maskp * np.arccos(x/np.sqrt(x**2 + y**2))

# Calculate theta
theta = np.arccos(z/r)

return r, theta, phi
44 changes: 44 additions & 0 deletions pyspedas/cotrans/sm2mlt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'''
Convert Solar Magnetic (SM) coordinates to Magnetic Local Time (MLT).

This function is similar to sm2mlt.pro in IDL SPEDAS.
'''
import numpy as np
from pyspedas import cart2spc


def sm2mlt(x_sm, y_sm, z_sm):
"""
Convert Solar Magnetic (SM) coordinates to Magnetic Local Time (MLT).

Parameters:
-----------
x_sm : float
X coordinate in Solar Magnetic system.
y_sm : float
Y coordinate in Solar Magnetic system.
z_sm : float
Z coordinate in Solar Magnetic system.

Returns:
--------
mlt_0 : float
Magnetic Local Time.

Notes:
------
- First, the SM coordinates are converted to spherical coordinates.
- The azimuthal angle from the conversion (phi) is then used to calculate MLT.
- If the resulting MLT is greater than 24, it is corrected by subtracting 24.
"""

# Convert to spherical coordinates
r, theta, phi = cart2spc(x_sm, y_sm, z_sm)

# Calculate initial MLT
mlt_0 = 12.0 + phi * 24.0 / (2.0 * np.pi)

# Fix MLTs greater than 24
mlt_0[mlt_0 > 24.0] -= 24.0

return mlt_0
43 changes: 43 additions & 0 deletions pyspedas/cotrans/spc2cart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'''
Convert spherical coordinates (r, theta, phi) to Cartesian coordinates (x, y, z).

This function is similar to spc2cart.pro in IDL SPEDAS.
'''
import numpy as np


def spc2cart(r, theta, phi):
"""
Convert spherical coordinates (r, theta, phi) to Cartesian coordinates (x, y, z).

Parameters:
-----------
r : float
Radial distance from the origin.
theta : float
Polar angle (angle from the positive z-axis to the point) in radians. It ranges from [0, pi].
phi : float
Azimuthal angle (angle in the xy-plane from the positive x-axis to the point) in radians. It ranges from [0, 2*pi].

Returns:
--------
x : float
X coordinate in Cartesian system.
y : float
Y coordinate in Cartesian system.
z : float
Z coordinate in Cartesian system.

Notes:
------
- Uses the following equations for conversion:
x = r*sin(theta)*cos(phi)
y = r*sin(theta)*sin(phi)
z = r*cos(theta)
"""
r, theta, phi = np.array(r), np.array(theta), np.array(phi)
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)

return x, y, z
47 changes: 34 additions & 13 deletions pyspedas/cotrans/tests/cotrans.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
from pyspedas.cotrans.cotrans import cotrans
from pyspedas.cotrans.fac_matrix_make import fac_matrix_make
from pytplot import get_data, store_data, del_data
from pyspedas import cotrans_get_coord, cotrans_set_coord
from pyspedas import cotrans_get_coord, cotrans_set_coord, sm2mlt


class CotransTestCases(unittest.TestCase):
"""Tests for cotrans."""

def test_fac_matrix_make(self):
doesntexist = fac_matrix_make('doesnt_exist')

Expand All @@ -37,19 +38,19 @@ def test_get_set_coord_wrappers(self):
self.assertTrue(setcoord)
after = cotrans_get_coord('test_coord')
self.assertTrue(after == 'GSE')
md = get_data('test_coord',metadata=True)
md = get_data('test_coord', metadata=True)
md['data_att']['units'] = 'km'
setcoord = cotrans_set_coord('test_coord', 'GSM')
self.assertTrue(setcoord)
md_after = get_data('test_coord',metadata=True)
md_after = get_data('test_coord', metadata=True)
after = cotrans_get_coord('test_coord')
self.assertTrue(after == 'GSM')
self.assertTrue(md_after['data_att']['units'] == 'km')
setcoord = cotrans_set_coord('doesnt_exist', 'GSM')

def test_get_set_coords(self):
""" Test for pytplot.set_coords/get_coords """
from pytplot import set_coords,get_coords
from pytplot import set_coords, get_coords

del_data()
doesntexist = get_coords('test_coord')
Expand All @@ -62,19 +63,19 @@ def test_get_set_coords(self):
self.assertTrue(setcoord)
after = get_coords('test_coord')
self.assertTrue(after == 'GSE')
md = get_data('test_coord',metadata=True)
md = get_data('test_coord', metadata=True)
md['data_att']['units'] = 'km'
setcoord = set_coords('test_coord', 'GSM')
self.assertTrue(setcoord)
md_after = get_data('test_coord',metadata=True)
md_after = get_data('test_coord', metadata=True)
after = get_coords('test_coord')
self.assertTrue(after == 'GSM')
self.assertTrue(md_after['data_att']['units'] == 'km')
setcoord = set_coords('doesnt_exist', 'GSM')

def test_get_set_units(self):
""" Test for pytplot.set_coords/get_coords """
from pytplot import set_units,get_units, set_coords, get_coords
from pytplot import set_units, get_units, set_coords, get_coords

del_data()
doesntexist = get_units('test_units')
Expand All @@ -86,19 +87,19 @@ def test_get_set_units(self):
self.assertTrue(setunits)
after = get_units('test_units')
self.assertTrue(after == 'Km')
set_coords('test_units','GEO')
set_coords('test_units', 'GEO')
setunits = set_units('test_units', 'mm')
self.assertTrue(setunits)
coords_after=get_coords('test_units')
units_after=get_units('test_units')
coords_after = get_coords('test_units')
units_after = get_units('test_units')
self.assertTrue(coords_after == 'GEO')
self.assertTrue(units_after == 'mm')

def test_dsl2gse(self):
"""Test themis.cotrans.dsl2gse."""
del_data()
# Try with missing variables. It should exit without problems.
dsl2gse('tha_fgl_dsl','tha_fgl_gse')
dsl2gse('tha_fgl_dsl', 'tha_fgl_gse')
# Now load the needed variables.
time_range = ['2017-03-23 00:00:00', '2017-03-23 23:59:59']
pyspedas.themis.state(probe='a', trange=time_range,
Expand Down Expand Up @@ -154,7 +155,7 @@ def test_cotrans_coord_mismatch(self):
# Metadata coordinate system is GEI, but requesting GSM->GEO transform. This should generate an error message
# and return failure.
result = cotrans(name_in=name_in, name_out=name_out,
coord_in="gsm", coord_out="geo")
coord_in="gsm", coord_out="geo")
self.assertTrue(result == 0)

def test_cotrans_igrf(self):
Expand Down Expand Up @@ -235,7 +236,7 @@ def test_all_cotrans(self):
count = 0
# Test non-existent systems.
result = cotrans(name_out=name1, time_in=t, data_in=d,
coord_in="badcoord", coord_out="gei")
coord_in="badcoord", coord_out="gei")
self.assertTrue(result == 0)
result = cotrans(name_out=name1, time_in=t, data_in=d,
coord_in="gei", coord_out="badcoord")
Expand Down Expand Up @@ -269,6 +270,26 @@ def test_all_cotrans(self):
self.assertTrue(abs(dd1[1]-dd2[1]) <= 1e-6)
self.assertTrue(abs(dd1[2]-dd2[2]) <= 1e-6)

def test_mlt(self):
'''Test sm2mlt.

Data is from goes18, 2023-01-01. [0, 100, 600, 1000]
'''

# SM coordinates and MLT values from IDL
sm_idl = [[33199.660, 25815.376, 2995.6487], [18814.747, 37614.243, 2990.7136],
[-40428.563, -11607.452, 2995.3227], [15798.372, -38974.509, 2995.0028]]
mlt_idl = [14.524527, 16.228377, 1.0679544, 7.4710163]

x = [item[0] for item in sm_idl]
y = [item[1] for item in sm_idl]
z = [item[2] for item in sm_idl]

mlt_python = sm2mlt(x, y, z)

for i in range(4):
self.assertTrue(abs(mlt_idl[i]-mlt_python[i]) <= 1e-6)


if __name__ == '__main__':
unittest.main()
Loading