From 7a6c6f45f989621ec23414134ebea0f004db057c Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 11 Apr 2024 15:44:06 -0400 Subject: [PATCH 01/60] ENH: created separate vector functions Created separate functions to convert vectors from one coordinate system to another. --- ocbpy/__init__.py | 1 + ocbpy/vectors.py | 645 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 646 insertions(+) create mode 100644 ocbpy/vectors.py diff --git a/ocbpy/__init__.py b/ocbpy/__init__.py index 0f7fbaea..e6765723 100644 --- a/ocbpy/__init__.py +++ b/ocbpy/__init__.py @@ -25,6 +25,7 @@ from ocbpy import ocb_correction # noqa F401 from ocbpy import ocb_scaling # noqa F401 from ocbpy import ocb_time # noqa F401 +from ocbpy import vectors # noqa F401 from ocbpy._boundary import DualBoundary # noqa F401 from ocbpy._boundary import EABoundary # noqa F401 diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py new file mode 100644 index 00000000..a9153828 --- /dev/null +++ b/ocbpy/vectors.py @@ -0,0 +1,645 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Full license can be found in License.md +# ---------------------------------------------------------------------------- +"""Functions for performing vector transformations.""" + +import numpy as np + +from ocbpy import ocb_time + + +def get_pole_loc(phi_cent, r_cent): + """Get the location of a pole of one coordinate system in another one. + + Parameters + ---------- + phi_cent : float or array-like + The angle from midnight in the base coordinate system of the destination + coordinate system pole in degrees + r_cent : float or array-like + The co-latitude in the base coordinate system of the destination + coordinate system pole in degrees + + Returns + ------- + pole_lt : float or array-like + The base coordinate system LT of the destination coordinate system pole + location in hours; float if both inputs are float-like + pole_lat : float or array-like + The base coordinate system latitude of the destination coordinate + system pole location in degrees; float if both inputs are float-like + + """ + pole_lt = ocb_time.deg2hr(np.asarray(phi_cent)) + pole_lat = 90.0 - np.asarray(r_cent) + + if len(pole_lt.shape) == 0 and len(pole_lat.shape) == 0: + pole_lt = float(pole_lt) + pole_lat = float(pole_lat) + + return pole_lt, pole_lat + + +def calc_vec_pole_angle(data_lt, data_lat, pole_lt, pole_lat): + """Find the angle between the base pole, data, and the other pole. + + Parameters + ---------- + data_lt : float or array-like + Local time of data in the base coordinate system in hours + data_lat : float or array-like + Latitude of data in the base coordinate system in degrees + pole_lt : float or array-like + Local time of destination pole location in the base coordinate system + in hours + pole_lat : float or array-like + Latitude of destination pole location in the base coordinate system in + degrees + + Returns + ------- + pole_angle : float or array-like + Angle between the base coordinate system pole, data location, and the + destination coordinate sytem pole (as located in the base coordinate + system) in degrees + + Notes + ----- + The data coordinates must be in the base coordinate system. Finds the + `pole_angle` using spherical trigonometry. + + """ + # Convert the local time values to radians, after calculating the + # difference between the destination pole and the data. + del_long = ocb_time.hr2rad(np.asarray(pole_lt) - np.asarray(data_lt)) + + if len(del_long.shape) == 0: + if del_long < -np.pi: + del_long += 2.0 * np.pi + else: + del_long[del_long < -np.pi] += 2.0 * np.pi + + # Initalize the output + pole_angle = np.full(shape=del_long.shape, fill_value=np.nan) + + # Assign the extreme values + if len(del_long.shape) == 0: + if del_long in [-np.pi, 0.0, np.pi]: + if abs(data_lat) > abs(pole_lat): + pole_angle = 180.0 + else: + pole_angle = 0.0 + return pole_angle + else: + zero_mask = (((del_long == 0) | (abs(del_long) == np.pi)) + & np.greater(abs(data_lat), abs(pole_lat), + where=~np.isnan(del_long))) + flat_mask = (((del_long == 0) | (abs(del_long) == np.pi)) + & np.less_equal(abs(data_lat), abs(pole_lat), + where=~np.isnan(del_long))) + + pole_angle[flat_mask] = 180.0 + pole_angle[zero_mask] = 0.0 + update_mask = (~zero_mask & ~flat_mask) + + if not np.any(update_mask): + return pole_angle + + # Find the distance in radians between the two poles + hemisphere = np.sign(pole_lat) + rad_pole = hemisphere * np.pi * 0.5 + del_pole = hemisphere * (rad_pole - np.radians(pole_lat)) + + # Get the distance in radians between the base pole and the data point + del_vect = hemisphere * (rad_pole - np.radians(data_lat)) + + # Use the Vincenty formula for a sphere + del_dest = np.arctan2( + np.sqrt((np.cos(np.radians(pole_lat)) * np.sin(del_long))**2 + + (np.cos(np.radians(data_lat)) * np.sin(np.radians(pole_lat)) + - np.sin(np.radians(data_lat)) * np.cos(np.radians(pole_lat)) + * np.cos(del_long))**2), + np.sin(np.radians(data_lat)) * np.sin(np.radians(pole_lat)) + + np.cos(np.radians(data_lat)) * np.cos(np.radians(pole_lat)) + * np.cos(del_long)) + + # Use the half-angle formula to get the pole angle + sum_sides = 0.5 * (del_vect + del_dest + del_pole) + half_angle = np.sqrt(np.sin(sum_sides) * np.sin(sum_sides - del_pole) + / (np.sin(del_vect) * np.sin(del_dest))) + + if pole_angle.shape == (): + pole_angle = np.degrees(2.0 * np.arccos(half_angle)) + else: + pole_angle[update_mask] = np.degrees(2.0 * np.arccos( + half_angle[update_mask])) + + return pole_angle + + +def define_pole_quadrants(data_lt, pole_lt, pole_angle): + """Define LT quadrants for the pole of the another coordinate system. + + Parameters + ---------- + data_lt : float or array-like + Local time of data in the base coordinate system in hours + pole_lt : float or array-like + Local time of destination pole location in the base coordinate system + in hours + pole_angle : float or array-like + Angle between the base coordinate system pole, data location, and the + destination coordinate sytem pole (as located in the base coordinate + system) in degrees + + Returns + ------- + pole_quad : float or array-like + Specifies the base coordinate system LT quadrant for each pole/data pair + + Notes + ----- + North (N) and East (E) are defined by the base coordinate system directions + centred on the the data vector location, assuming vertical is positive + downwards. Quadrants: 1 [N, E]; 2 [N, W]; 3 [S, W]; 4 [S, E]; 0 [undefined] + + """ + # Initalize the output + pole_quad = np.zeros(shape=np.asarray(pole_angle).shape) + + # Determine where the destination pole is relative to the data vector + del_lt = np.asarray(pole_lt) - np.asarray(data_lt) + + neg_mask = np.less(del_lt, 0.0, where=~np.isnan(del_lt)) & ~np.isnan(del_lt) + while np.any(neg_mask): + if len(del_lt.shape) == 0: + del_lt += 24.0 + neg_mask = [False] + else: + del_lt[neg_mask] += 24.0 + neg_mask = np.less(del_lt, 0.0, + where=~np.isnan(del_lt)) & ~np.isnan(del_lt) + + large_mask = np.greater_equal(abs(del_lt), 24.0, + where=~np.isnan(del_lt)) & ~np.isnan(del_lt) + if np.any(large_mask): + if len(del_lt.shape) == 0: + del_lt -= 24.0 * np.sign(del_lt) + else: + del_lt[large_mask] -= 24.0 * np.sign(del_lt[large_mask]) + + # Find the quadrant in which the OCB pole lies + nan_mask = ~np.isnan(pole_angle) & ~np.isnan(del_lt) + quad1_mask = np.less(pole_angle, 90.0, where=nan_mask) & np.less( + del_lt, 12.0, where=nan_mask) & nan_mask + quad2_mask = np.less(pole_angle, 90.0, where=nan_mask) & np.greater_equal( + del_lt, 12.0, where=nan_mask) & nan_mask + quad3_mask = np.greater_equal( + pole_angle, 90.0, where=nan_mask) & np.greater_equal( + del_lt, 12.0, where=nan_mask) & nan_mask + quad4_mask = np.greater_equal(pole_angle, 90.0, where=nan_mask) & np.less( + del_lt, 12.0, where=nan_mask) & nan_mask + + if len(pole_quad.shape) == 0: + if np.all(quad1_mask): + pole_quad = np.asarray(1) + elif np.all(quad2_mask): + pole_quad = np.asarray(2) + elif np.all(quad3_mask): + pole_quad = np.asarray(3) + elif np.all(quad4_mask): + pole_quad = np.asarray(4) + else: + pole_quad[quad1_mask] = 1 + pole_quad[quad2_mask] = 2 + pole_quad[quad3_mask] = 3 + pole_quad[quad4_mask] = 4 + + return pole_quad + + +def define_vect_quadrants(vect_n, vect_e): + """Define LT quadrants for the data vectors. + + Parameters + ---------- + vect_n : float or array-like + North component of data vector in the base coordinate system in units + of degrees latitude + vect_e : float or array-like + East component of data vector in the base coordinate system in units of + degrees latitude + + Returns + ------- + vect_quad : float or array-like + Specifies the base coordinate system LT quadrant for each data vector + + Notes + ----- + North (N) and East (E) are defined by the base coordinate system directions + centred on the data vector location, assuming vertical is positive downwards + Quadrants: 1 [N, E]; 2 [N, W]; 3 [S, W]; 4 [S, E]; 0 [undefined] + + """ + # Get the masks for non-fill values in each quadrant + nan_mask = ~np.isnan(vect_n) & ~np.isnan(vect_e) + quad1_mask = np.greater_equal( + vect_n, 0.0, where=nan_mask) & np.greater_equal( + vect_e, 0.0, where=nan_mask) & nan_mask + quad2_mask = np.greater_equal(vect_n, 0.0, where=nan_mask) & np.less( + vect_e, 0.0, where=nan_mask) & nan_mask + quad3_mask = np.less(vect_n, 0.0, where=nan_mask) & np.less( + vect_e, 0.0, where=nan_mask) & nan_mask + quad4_mask = np.less(vect_n, 0.0, where=nan_mask) & np.greater_equal( + vect_e, 0.0, where=nan_mask) & nan_mask + + # Initialize the output + vect_quad = np.zeros(shape=nan_mask.shape) + + # Assign the quandrants to the output + if len(vect_quad.shape) == 0: + if np.all(quad1_mask): + vect_quad = np.asarray(1) + elif np.all(quad2_mask): + vect_quad = np.asarray(2) + elif np.all(quad3_mask): + vect_quad = np.asarray(3) + elif np.all(quad4_mask): + vect_quad = np.asarray(4) + else: + vect_quad[quad1_mask] = 1 + vect_quad[quad2_mask] = 2 + vect_quad[quad3_mask] = 3 + vect_quad[quad4_mask] = 4 + + return vect_quad + + +def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): + """Calcuate the North azimuth angle for the destination pole. + + Parameters + ---------- + pole_quad : float or array-like + Specifies the base coordinate system LT quadrant for each pole/data pair + vect_quad : float or array-like + Specifies the base coordinate system LT quadrant for each data vector + base_naz_angle : float or array-like + North azimuth angle for the base coordinate system pole in degrees + pole_angle: float or array-like + Angle between the base coordinate system pole, data location, and the + destination coordinate sytem pole (as located in the base coordinate + system) in degrees + + Returns + ------- + dest_naz_angle : float or array-like + North azimuth angle for the destination coordinate system pole in + degrees + + Raises + ------ + ValueError + If the input quadrant data is undefined + + """ + quad_range = np.arange(1, 5) + + # Test input + if not np.any(np.isin(pole_quad, quad_range)): + raise ValueError("destination coordinate pole quadrant is undefined") + + if not np.any(np.isin(vect_quad, quad_range)): + raise ValueError("data vector quadrant is undefined") + + # Initialise the output and set the quadrant dictionary + nan_mask = ~np.isnan(base_naz_angle) & ~np.isnan(pole_angle) + dest_naz_angle = np.full(shape=(base_naz_angle + pole_angle).shape, + fill_value=np.nan) + quads = {oquad: {vquad: + (pole_quad == oquad) & (vect_quad == vquad) & nan_mask + for vquad in quad_range} for oquad in quad_range} + + # Create masks for the different quadrant combinations + abs_mask = quads[1][1] | quads[2][2] | quads[3][3] | quads[4][4] + add_mask = (quads[1][2] | quads[1][3] | quads[2][1] | quads[2][4] + | quads[3][1] | quads[4][2]) + mpa_mask = quads[1][4] | quads[2][3] + maa_mask = quads[3][2] | quads[4][1] + cir_mask = quads[3][4] | quads[4][3] + + # Calculate OCB polar angle based on the quadrants and other angles + if np.any(abs_mask): + if len(dest_naz_angle.shape) == 0: + dest_naz_angle = abs(base_naz_angle - pole_angle) + else: + dest_naz_angle[abs_mask] = abs(base_naz_angle + - pole_angle)[abs_mask] + + if np.any(add_mask): + if len(dest_naz_angle.shape) == 0: + dest_naz_angle = pole_angle + base_naz_angle + if dest_naz_angle > 180.0: + dest_naz_angle = 360.0 - dest_naz_angle + else: + dest_naz_angle[add_mask] = (pole_angle + base_naz_angle)[add_mask] + lmask = (dest_naz_angle > 180.0) & add_mask + if np.any(lmask): + dest_naz_angle[lmask] = 360.0 - dest_naz_angle[lmask] + + if np.any(mpa_mask): + if len(dest_naz_angle.shape) == 0: + dest_naz_angle = base_naz_angle - pole_angle + else: + dest_naz_angle[mpa_mask] = (base_naz_angle - pole_angle)[mpa_mask] + + if np.any(maa_mask): + if len(dest_naz_angle.shape) == 0: + dest_naz_angle = pole_angle - base_naz_angle + else: + dest_naz_angle[maa_mask] = (pole_angle - base_naz_angle)[maa_mask] + + if np.any(cir_mask): + if len(dest_naz_angle.shape) == 0: + dest_naz_angle = 360.0 - base_naz_angle - pole_angle + else: + dest_naz_angle[cir_mask] = (360.0 - base_naz_angle - pole_angle)[ + cir_mask] + + return dest_naz_angle + + +def calc_dest_vec_sign(pole_quad, vect_quad, base_naz_angle, pole_angle, + north=False, east=False, quads=None): + """Calculate the sign of the North and East components. + + Parameters + ---------- + pole_quad : float or array-like + Specifies the base coordinate system LT quadrant for each pole/data pair + vect_quad : float or array-like + Specifies the base coordinate system LT quadrant for each data vector + base_naz_angle : float or array-like + North azimuth angle for the base coordinate system pole in degrees + pole_angle: float or array-like + Angle between the base coordinate system pole, data location, and the + destination coordinate sytem pole (as located in the base coordinate + system) in degrees + north : bool + Get the sign of the north component(s) (default=False) + east : bool + Get the sign of the east component(s) (default=False) + quads : dict or NoneType + Dictionary of boolean values or arrays of boolean values for the + destination coordinate system pole and data vector quadrants + (default=None) + + Returns + ------- + vsigns : dict + Dictionary with keys 'north' and 'east' containing the desired signs + + Raises + ------ + ValueError + If the input quadrant data is undefined + + """ + quad_range = np.arange(1, 5) + + # Test input + if not np.any(np.isin(pole_quad, quad_range)): + raise ValueError("destination coordinate pole quadrant is undefined") + + if not np.any(np.isin(vect_quad, quad_range)): + raise ValueError("data vector quadrant is undefined") + + # If necessary, initialise quadrant dictionary + nan_mask = ~np.isnan(base_naz_angle) & ~np.isnan(pole_angle) + if quads is None or not np.all([kk in quads.keys() for kk in quad_range]): + quads = {o: {v: (pole_quad == o) & (vect_quad == v) + & nan_mask for v in quad_range} for o in quad_range} + + # Initialise output + vsigns = {"north": np.zeros(shape=quads[1][1].shape), + "east": np.zeros(shape=quads[1][1].shape)} + + # Determine the desired vector signs + if north: + pole_minus = pole_angle - 90.0 + minus_pole = 90.0 - pole_angle + pole_plus = pole_angle + 90.0 + + pmask = (quads[1][1] | quads[2][2] | quads[3][3] | quads[4][4] + | ((quads[1][4] | quads[2][3]) & np.less_equal( + base_naz_angle, pole_plus, where=nan_mask)) + | ((quads[1][2] | quads[2][1]) & np.less_equal( + base_naz_angle, minus_pole, where=nan_mask)) + | ((quads[3][4] | quads[4][3]) & np.greater_equal( + base_naz_angle, 180.0 - pole_minus, where=nan_mask)) + | ((quads[3][2] | quads[4][1]) & np.greater_equal( + base_naz_angle, pole_minus, where=nan_mask))) + + if np.any(pmask): + if len(vsigns["north"].shape) == 0: + vsigns["north"] = 1 + else: + vsigns["north"][pmask] = 1 + + if np.any(~pmask): + if len(vsigns["north"].shape) == 0: + vsigns["north"] = -1 + else: + vsigns["north"][~pmask] = -1 + + if east: + minus_pole = 180.0 - pole_angle + + pmask = (quads[1][4] | quads[2][1] | quads[3][2] | quads[4][3] + | ((quads[1][1] | quads[4][4]) & np.greater_equal( + base_naz_angle, pole_angle, where=nan_mask)) + | ((quads[3][1] | quads[2][4]) & np.less_equal( + base_naz_angle, minus_pole, where=nan_mask)) + | ((quads[4][2] | quads[1][3]) & np.greater_equal( + base_naz_angle, minus_pole, where=nan_mask)) + | ((quads[2][2] | quads[3][3]) & np.less_equal( + base_naz_angle, pole_angle, where=nan_mask))) + + if np.any(pmask): + if len(vsigns["east"].shape) == 0: + vsigns["east"] = 1 + else: + vsigns["east"][pmask] = 1 + + if np.any(~pmask): + if len(vsigns["east"].shape) == 0: + vsigns["east"] = -1 + else: + vsigns["east"][~pmask] = -1 + + return vsigns + + +def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, + pole_lt, pole_lat, pole_angle, pole_quad): + """Adjust a vector from one coordinate system to another. + + Parameters + ---------- + vect_lt : float or array-like + Vector local time in base coordinate system in hours + vect_lat : float or array-like + Vector latitude in base coordinate system in degrees + vect_n : float or array-like + Vector North component in base coordinate system in degrees latitude + vect_e : float or array-like + Vector East component in base coordinate system in degrees latitude + vect_z : float or array-like + Vector vertical component in base coordinate system in degrees latitude + vect_quad : float or array-like + Specifies the base coordinate system LT quadrant for each data vector + pole_lt : float or array-like + Local time of destination pole location in the base coordinate system + in hours + pole_lat : float or array-like + Latitude of destination pole location in the base coordinate system in + degrees + pole_angle : float or array-like + Angle between the base coordinate system pole, data location, and the + destination coordinate sytem pole (as located in the base coordinate + system) in degrees + pole_quad : float or array-like + Specifies the base coordinate system LT quadrant for each pole/data pair + + Returns + ------- + dest_n : float or array-like + Vector North component in destination coordinate system in degrees + latitude + dest_e : float or array-like + Vector East component in destination coordinate system in degrees + latitude + dest_z : float or array-like + Vector vertical component in destination coordinate system in degrees + latitude + + """ + # Initialize the output + dest_n = np.full(shape=np.asarray(vect_n).shape, fill_value=np.nan) + dest_e = np.full(shape=np.asarray(vect_e).shape, fill_value=np.nan) + dest_z = np.full(shape=np.asarray(vect_z).shape, fill_value=np.nan) + + # Determine the special case assignments + zero_mask = (vect_n == 0.0) & (vect_e == 0.0) + ns_mask = (pole_angle == 0.0) | (pole_angle == 180.0) + norm_mask = ~(zero_mask + ns_mask) + + # There's nothing to adjust if there is no magnitude + if np.any(zero_mask): + if len(zero_mask.shape) == 0: + dest_n = np.zeros(shape=dest_n.shape) + dest_e = np.zeros(shape=dest_e.shape) + dest_z = np.zeros(shape=dest_z.shape) + else: + dest_n[zero_mask] = 0.0 + dest_e[zero_mask] = 0.0 + dest_z[zero_mask] = 0.0 + + # The measurement is aligned with the base and destination poles + if np.any(ns_mask): + if len(vect_n.shape) == 0: + dest_n = np.full(shape=dest_n.shape, fill_value=vect_n) + dest_e = np.full(shape=dest_e.shape, fill_value=vect_e) + dest_z = np.full(shape=dest_z.shape, fill_value=vect_z) + else: + dest_n[ns_mask] = vect_n[ns_mask] + dest_e[ns_mask] = vect_e[ns_mask] + dest_z[ns_mask] = vect_z[ns_mask] + + # Determine if the measurement is on or between the poles. This does + # not affect the vertical direction + sign_mask = (pole_angle == 0.0) & np.greater_equal( + vect_lat, pole_lat, where=~np.isnan(vect_lat)) & ~np.isnan(vect_lat) + + if np.any(sign_mask): + if dest_n.shape == (): + dest_n *= -1.0 + dest_e *= -1.0 + else: + dest_n[sign_mask] *= -1.0 + dest_e[sign_mask] *= -1.0 + + # If there are still undefined vectors, assign them using the typical case + if np.any(norm_mask): + # If not defined, get the pole and vector quadrants + if len(vect_quad.shape) == 0 and vect_quad == 0 or ( + len(vect_quad.shape) > 0 and np.any(vect_quad[norm_mask] == 0)): + vect_quad = define_vect_quadrants(vect_n, vect_e) + + if (len(pole_quad.shape) == 0 and pole_quad == 0) or ( + len(pole_quad.shape) > 0 and np.any(pole_quad[norm_mask] == 0)): + pole_quad = define_pole_quadrants(vect_lt, pole_lt, pole_angle) + + # Get the unscaled 2D vector magnitude and calculate the AACGM north + # azimuth in degrees + if len(vect_n.shape) == 0: + vmag = np.sqrt(vect_n**2 + vect_e**2) + base_naz_angle = np.degrees(np.arccos(vect_n / vmag)) + else: + vmag = np.sqrt(vect_n[norm_mask]**2 + vect_e[norm_mask]**2) + base_naz_angle = np.full(shape=norm_mask.shape, fill_value=np.nan) + base_naz_angle[norm_mask] = np.degrees(np.arccos(vect_n[norm_mask] + / vmag)) + + # Get the destination coordinate system north azimuth in radians + dest_naz_angle = np.radians(calc_dest_polar_angle( + pole_quad, vect_quad, base_naz_angle, pole_angle)) + + # Get the sign of the North and East components + vsigns = calc_dest_vec_sign(pole_quad, vect_quad, base_naz_angle, + pole_angle, north=True, east=True) + + # Scale the vector along the OCB north + if len(vect_z.shape) == 0: + vz = vect_z + else: + vz = vect_z[norm_mask] + nan_mask = np.isnan(vmag) | ( + np.isnan(dest_naz_angle) if len(dest_naz_angle.shape) == 0 + else np.isnan(dest_naz_angle[norm_mask])) + vz[nan_mask] = np.nan + + # Restrict the OCB angle to result in positive sines and cosines + lmask = dest_naz_angle > np.pi / 2.0 + if np.any(lmask): + if len(dest_naz_angle.shape) == 0: + dest_naz_angle = np.pi - dest_naz_angle + else: + dest_naz_angle[lmask] = np.pi - dest_naz_angle[lmask] + + # Calculate the vector components + if len(vmag.shape) == 0: + if len(dest_naz_angle.shape) == 0: + dest_n = np.full(shape=dest_n.shape, fill_value=( + vsigns['north'] * vmag * np.cos(dest_naz_angle))) + dest_e = np.full(shape=dest_e.shape, fill_value=( + vsigns['east'] * vmag * np.sin(dest_naz_angle))) + dest_z = np.full(shape=dest_z.shape, fill_value=vz) + else: + nval = vsigns['north'][norm_mask] * vmag * np.cos( + dest_naz_angle)[norm_mask] + dest_n = np.full(shape=nval.shape, fill_value=nval) + dest_e = np.full(shape=nval.shape, fill_value=( + vsigns['east'][norm_mask] * vmag * np.sin( + dest_naz_angle)[norm_mask])) + dest_z = np.full(shape=nval.shape, fill_value=vz) + else: + dest_n[norm_mask] = vsigns['north'][norm_mask] * vmag * np.cos( + dest_naz_angle)[norm_mask] + dest_e[norm_mask] = vsigns['east'][norm_mask] * vmag * np.sin( + dest_naz_angle)[norm_mask] + dest_z[norm_mask] = vz + + return dest_n, dest_e, dest_z From 70126ed30f65f1de52863e61b9d9a1704d934d4b Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 11 Apr 2024 15:45:19 -0400 Subject: [PATCH 02/60] ENH: implemented geographic vector inputs Adapted VectorData to: - accept geodetic or geocentric coordinates as vector inputs, - use the new vector functions in the methods, and - simplify the print output to be more streamlined. --- ocbpy/ocb_scaling.py | 1313 +++++++++++++++++++++--------------------- 1 file changed, 670 insertions(+), 643 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 0648a5a5..2798413f 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -13,9 +13,13 @@ """ +import aacgmv2 import numpy as np +import warnings import ocbpy +from ocbpy import ocb_time +from ocbpy import vectors class VectorData(object): @@ -28,23 +32,32 @@ class VectorData(object): ocb_ind : int or array-like OCBoundary or DualBoundary record index matched to this data index (zero offset) - aacgm_lat : float or array-like - Vector AACGM latitude (degrees) - aacgm_mlt : float or array-like - Vector AACGM MLT (hours) + lat : float or array-like + Vector latitude (degrees) + lt : float or array-like + Vector LT (hours) + height : float or array-like + Geocentric height above sea level (km) at which magnetic coordinates + will be calculated if conversion is needed (default=350.0) + loc_coord : str + Name of the coordinate system for `lat` and `lt`; one of 'magnetic', + 'geocentric', or 'geodetic' (default='magnetic') ocb_lat : float or array-like Vector OCB latitude (degrees) (default=np.nan) ocb_mlt : float or array-like Vector OCB MLT (hours) (default=np.nan) - aacgm_n : float or array-like - AACGM North pointing vector (positive towards North) (default=0.0) - aacgm_e : float or array-like - AACGM East pointing vector (completes right-handed coordinate system + vect_n : float or array-like + Vector North-pointing component (positive towards North) (default=0.0) + vect_e : float or array-like + Vector East-pointing component (completes right-handed coordinate system (default = 0.0) - aacgm_z : float or array-like - AACGM Vertical pointing vector (positive down) (default=0.0) - aacgm_mag : float or array-like + vect_z : float or array-like + Vector vertical-pointing component (positive down) (default=0.0) + vect_mag : float or array-like Vector magnitude (default=np.nan) + vect_coord : str + Name of the coordinate system for `vect_n` and `vect_e`; one of + 'magnetic', 'geocentric', or 'geodetic' (default='magnetic') dat_name : str Data name (default=None) dat_units : str @@ -53,6 +66,9 @@ class VectorData(object): Function for scaling AACGM magnitude with arguements: [measurement value, mesurement AACGM latitude (degrees), mesurement OCB latitude (degrees)] (default=None) + **kwargs : dict + Accepts deprecated parameters: `aacgm_lat`, `aacgm_mlt`, `aacgm_n`, + `aacgm_e`, `aacgm_z`, and `aacgm_mag`. Attributes ---------- @@ -78,7 +94,8 @@ class VectorData(object): Angle at vector location appended by AACGM and OCB poles in degrees (default=np.nan) aacgm_naz : float or array-like - AACGM north azimuth of data vector in degrees (default=np.nan) + AACGM north azimuth of data vector in degrees; deprecated + (default=np.nan) ocb_aacgm_lat : float or array-like AACGM latitude of OCB pole in degrees (default=np.nan) ocb_aacgm_mlt : float or array-like @@ -88,12 +105,21 @@ class VectorData(object): ----- May only handle one data type, so scale_func cannot be an array + Warnings + -------- + DeprecationWarning + Several kwargs/attributes and method have been changed to reflect new + allowed input types (data in geodetic or geographic coordinates). + Support for the old parameters and methods will be removed in + version 0.4.1+. + """ - def __init__(self, dat_ind, ocb_ind, aacgm_lat, aacgm_mlt, ocb_lat=np.nan, - ocb_mlt=np.nan, r_corr=np.nan, aacgm_n=0.0, aacgm_e=0.0, - aacgm_z=0.0, aacgm_mag=np.nan, dat_name=None, dat_units=None, - scale_func=None): + def __init__(self, dat_ind, ocb_ind, lat, lt, height=350.0, + loc_coord='magnetic', ocb_lat=np.nan, ocb_mlt=np.nan, + r_corr=np.nan, vect_n=0.0, vect_e=0.0, vect_z=0.0, + vect_mag=np.nan, vect_coord='magnetic', dat_name=None, + dat_units=None, scale_func=None, **kwargs): # Assign the vector data name and units self.dat_name = dat_name @@ -103,19 +129,58 @@ def __init__(self, dat_ind, ocb_ind, aacgm_lat, aacgm_mlt, ocb_lat=np.nan, self.dat_ind = dat_ind self.ocb_ind = ocb_ind - # Assign the AACGM vector values and location - self.aacgm_n = aacgm_n - self.aacgm_e = aacgm_e - self.aacgm_z = aacgm_z - self.aacgm_lat = aacgm_lat - self.aacgm_mlt = aacgm_mlt + # Assign the AACGM vector values, coordinates, and location + self.vect_n = vect_n + self.vect_e = vect_e + self.vect_z = vect_z + self.lat = lat + self.lt = lt + self.height = height + self.loc_coord = loc_coord.lower() + self.vect_coord = vect_coord.lower() + + # Check for deprecated values + set_mag = True + if len(kwargs.keys()) > 0: + used_dep = list() + dep_pairs = {'aacgm_n': 'vect_n', 'aacgm_e': 'vect_e', + 'aacgm_lat': 'lat', 'aacgm_mlt': 'lt', + 'aacgm_z': 'vect_z', 'aacgm_mag': 'vect_mag'} + for dep_key in dep_pairs.keys(): + if dep_key in kwargs.keys(): + # Save the deprecated kwarg to raise a single warning later + used_dep.append(dep_key) + + # Update the new attribute + setattr(self, dep_pairs[dep_key], kwargs[dep_key]) + + if dep_key == 'aacgm_mag': + set_mag = False + + # Raise a warning + if len(used_dep) == 0: + ocbpy.logger.warning('unknown kwargs, ignored: {:}'.format( + kwargs)) + else: + new_kwargs = [dep_pairs[dep_key] for dep_key in used_dep] + warnings.warn("".join(['kwargs have been replaced with new ', + 'names, that reflect their new ', + 'flexibility. Old kwargs will be ', + 'removed in version 0.4.1+. Old kwargs ', + 'used: ', repr(used_dep), '; replace ', + 'with: ', repr(new_kwargs)]), + DeprecationWarning, stacklevel=2) + + if set_mag: + # Set the magnitude if the deprecated kwarg was not supplied + self.vect_mag = vect_mag + + # Test the coordinate systems for valid options + self._test_coords() # Test the initalization shape and update the vector shapes if needed self._test_update_vector_shape() - # Assign the vector magnitude(s) - self.aacgm_mag = aacgm_mag - # Assign the OCB vector default values self.ocb_lat = ocb_lat self.ocb_mlt = ocb_mlt @@ -143,13 +208,15 @@ def __repr__(self): # Format the base output out = "".join(["ocbpy.ocb_scaling.VectorData(", repr(self.dat_ind), - ", ", repr(self.ocb_ind), ", ", repr(self.aacgm_lat), - ", ", repr(self.aacgm_mlt), ", ocb_lat=", + ", ", repr(self.ocb_ind), ", ", repr(self.lat), + ", ", repr(self.lt), ", height=", repr(self.height), + ", loc_coord=", repr(self.loc_coord), ", ocb_lat=", repr(self.ocb_lat), ", ocb_mlt=", repr(self.ocb_mlt), - ", r_corr=", repr(self.r_corr), ", aacgm_n=", - repr(self.aacgm_n), ", aacgm_e=", repr(self.aacgm_e), - ", aacgm_z=", repr(self.aacgm_z), ", aacgm_mag=", - repr(self.aacgm_mag), ", dat_name=", + ", r_corr=", repr(self.r_corr), ", vect_n=", + repr(self.vect_n), ", vect_e=", repr(self.vect_e), + ", vect_z=", repr(self.vect_z), ", vect_mag=", + repr(self.vect_mag), ", vect_coord=", + repr(self.vect_coord), ", dat_name=", repr(self.dat_name), ", dat_units=", repr(self.dat_units), ", scale_func=", repr_func, ")"]) @@ -161,81 +228,93 @@ def __repr__(self): def __str__(self): """Provide user readable representation of the VectorData object.""" - out = "Vector data:" - if self.dat_name is not None: - out += " {:s}".format(self.dat_name) - if self.dat_units is not None: - out += " ({:s})".format(self.dat_units) + out = "".join([ + "Vector data:", + "" if self.dat_name is None else " {:s}".format(self.dat_name), + "" if self.dat_units is None else " ({:s})".format(self.dat_units), + "\nData Index {:}\tBoundary Index ".format(self.dat_ind), + "{:}\n-------------------------------------------".format( + self.ocb_ind)]) - out += "\nData Index {:}\tBoundary Index {:}\n".format(self.dat_ind, - self.ocb_ind) - out += "-------------------------------------------\n" - - # Print AACGM vector location(s) + # Print vector location(s) if self.dat_ind.shape == () and self.ocb_ind.shape == (): - out += "Locations: [Mag. Lat. (degrees), MLT (hours)]\n" - out += " AACGM: [{:.3f}, {:.3f}]\n".format(self.aacgm_lat, - self.aacgm_mlt) - out += " OCB: [{:.3f}, {:.3f}]\n".format(self.ocb_lat, - self.ocb_mlt) + out = "\n".join([ + out, "Locations: [Lat. (degrees), LT (hours), Alt (km)]", + "{:s}: [{:.3f}, {:.3f}, {:.1f}]".format( + self.loc_coord.rjust(9), self.lat, self.lt, self.height), + " OCB: [{:.3f}, {:.3f}, N/A]".format(self.ocb_lat, + self.ocb_mlt)]) else: - out += "Locations: [Mag. Lat. (degrees), MLT (hours), Index]\n" - if self.dat_ind.shape == self.ocb_ind.shape: - for i, dind in enumerate(self.dat_ind): - out += " AACGM: [{:.3f}, {:.3f}, {:d}]\n".format( - self.aacgm_lat[i], self.aacgm_mlt[i], dind) - out += " OCB: [{:.3f}, {:.3f}, {:d}]\n".format( - self.ocb_lat[i], self.ocb_mlt[i], self.ocb_ind[i]) - elif self.ocb_ind.shape == (): + out = '\n'.join([ + out, + "Locations: [Lat. (degrees), LT (hours), Alt (km), Index]"]) + + if self.dat_ind.shape == self.ocb_ind.shape or len( + self.ocb_ind.shape) == 0: for i, dind in enumerate(self.dat_ind): - out += " AACGM: [{:.3f}, {:.3f}, {:d}]\n".format( - self.aacgm_lat[i], self.aacgm_mlt[i], dind) - if self.ocb_lat.shape == () and np.isnan(self.ocb_lat): - out += " OCB: [nan, nan, {:d}]\n".format( + if len(self.ocb_lat.shape) == 0 and np.isnan(self.ocb_lat): + ocb_line = " OCB: [nan, nan, N/A, {:d}]".format( self.ocb_ind) else: - out += " OCB: [{:.3f}, {:.3f}, {:d}]\n".format( - self.ocb_lat[i], self.ocb_mlt[i], self.ocb_ind) + ocb_line = "".join([ + " OCB: [{:.3f}, ".format(self.ocb_lat[i]), + "{:.3f}, ".format(self.ocb_mlt[i]), + "N/A, {:d}]".format( + self.ocb_ind if len(self.ocb_ind.shape) == 0 + else self.ocb_ind[i])]) + out = '\n'.join([ + out, "{:s}: [{:.3f}, {:.3f}, {:.1f}, {:d}]".format( + self.loc_coord.rjust(9), self.lat[i], self.lt[i], + self.height[i], dind), ocb_line]) else: - out += " AACGM: [{:.3f}, {:.3f}, {:d}]\n".format( - self.aacgm_lat, self.aacgm_mlt, self.dat_ind) + out = '\n'.join([ + out, "{:s}: [{:.3f}, {:.3f}, {:.1f}, {:d}]".format( + self.loc_coord.rjust(9), self.lat, self.lt, + self.height, self.dat_ind)]) for i, oind in enumerate(self.ocb_ind): - out += " OCB: [{:.3f}, {:.3f}, {:d}]\n".format( - self.ocb_lat[i], self.ocb_mlt[i], oind) - - out += "\n-------------------------------------------\n" - if self.aacgm_mag.shape == () and self.ocb_mag.shape == (): - out += "Value: Magnitude [N, E, Z]\n" - out += "AACGM: {:.3g} [{:.3g}".format(self.aacgm_mag, self.aacgm_n) - out += ", {:.3g}, {:.3g}]\n".format(self.aacgm_e, self.aacgm_z) + out = '\n'.join([ + out, " OCB: [{:.3f}, {:.3f}, N/A, {:d}]\n".format( + self.ocb_lat[i], self.ocb_mlt[i], oind)]) + + out = "\n".join([out, "-------------------------------------------"]) + if self.vect_mag.shape == () and self.ocb_mag.shape == (): + out = '\n'.join([out, " Value: Mag. [N, E, Z]", + "{:s}: {:.3g} [{:.3g}, {:.3g}, {:.3g}]".format( + self.vect_coord, self.vect_mag, self.vect_n, + self.vect_e, self.vect_z)]) if not np.isnan(self.ocb_mag): - out += " OCB: {:.3g} [{:.3g}".format(self.ocb_mag, self.ocb_n) - out += ", {:.3g}, {:.3g}]\n".format(self.ocb_e, self.ocb_z) + out = '\n'.join([ + out, " OCB: {:.3g} [{:.3g}, {:.3g}, {:.3g}]".format( + self.ocb_mag, self.ocb_n, self.ocb_e, self.ocb_z)]) else: - out += "Value: Magnitude [N, E, Z] Index\n" + out = '\n'.join([out, " Value: Mag. [N, E, Z] Index"]) for i, mag in enumerate(self.ocb_mag): - if self.aacgm_mag.shape == () and i == 0: - out += "AACGM: {:.3g} [".format(self.aacgm_mag) - out += "{:.3g}, {:.3g}, {:.3g}] {:d}\n".format( - self.aacgm_n, self.aacgm_e, self.aacgm_z, self.dat_ind) - elif self.aacgm_mag.shape != (): - out += "AACGM: {:.3g} [".format(self.aacgm_mag[i]) - out += "{:.3g}, {:.3g}, {:.3g}] ".format( - self.aacgm_n[i], self.aacgm_e[i], self.aacgm_z[i]) - out += "{:d}\n".format(self.dat_ind[i]) - + if self.vect_mag.shape == () and i == 0: + vec_line = ''.join([ + self.vect_coord, ": {:.3g}".format(self.vect_mag), + " [{:.3g}, {:.3g}, ".format(self.vect_n, self.vect_e), + "{:.3g}] {:d}".format(self.vect_z, self.dat_ind)]) + elif self.vect_mag.shape != (): + vec_line = ''.join([ + self.vect_coord, ": {:.3g} [".format(self.vect_mag[i]), + "{:.3g}, {:.3g}".format(self.vect_n[i], self.vect_e[i]), + ", {:.3g}] {:d}".format(self.vect_z[i], + self.dat_ind[i])]) if not np.isnan(mag): - out += " OCB: {:.3g} [{:.3g}, ".format(mag, self.ocb_n[i]) - out += "{:.3g}, ".format(self.ocb_e[i]) - out += "{:.3g}] {:d}\n".format( - self.ocb_z[i], self.ocb_ind if self.ocb_ind.shape == () - else self.ocb_ind[i]) - - out += "\n-------------------------------------------\n" - if self.scale_func is None: - out += "No magnitude scaling function provided\n" - else: - out += "Scaling function: {:s}\n".format(self.scale_func.__name__) + vec_line = "".join([ + vec_line, "\n OCB: {:.3g} [".format(mag), + "{:.3g}, {:.3g}, ".format(self.ocb_n[i], self.ocb_e[i]), + "{:.3g}] {:d}".format( + self.ocb_z[i], self.ocb_ind + if self.ocb_ind.shape == () else self.ocb_ind[i])]) + out = '\n'.join([out, vec_line]) + + # Print the scaling information + out = "\n".join([out, "-------------------------------------------", + "No magnitude scaling function provided" + if self.scale_func is None else + "Scaling function: {:s}\n".format( + self.scale_func.__name__)]) return out @@ -257,6 +336,17 @@ def __setattr__(self, name, value): if type_str.find('int') < 0 and type_str.find('float') < 0: out_val = value + # TODO(#133): remove after old attributes are deprecated + dep_pairs = {'aacgm_n': 'vect_n', 'aacgm_e': 'vect_e', + 'aacgm_lat': 'lat', 'aacgm_mlt': 'lt', + 'aacgm_z': 'vect_z', 'aacgm_mag': 'vect_mag'} + if name in dep_pairs.keys(): + warnings.warn("".join([name, ' has been replaced with ', + dep_pairs[name], '. Old attribute will be ', + 'removed in version 0.4.1+.']), + DeprecationWarning, stacklevel=2) + name = dep_pairs[name] + # Use Object to avoid recursion super(VectorData, self).__setattr__(name, out_val) return @@ -268,8 +358,8 @@ def _ocb_attr_setter(self, ocb_name, ocb_val): ---------- ocb_name : str OCB attribute name - value - Value (any type) to be assigned to attribute specified by name + ocb_val : any + Value to be assigned to attribute specified by name """ # Ensure the shape is correct @@ -279,6 +369,52 @@ def _ocb_attr_setter(self, ocb_name, ocb_val): self.__setattr__(ocb_name, ocb_val) return + def _dat_attr_setter(self, dat_name, dat_val): + """Set data attributes. + + Parameters + ---------- + dat_name : str + OCB attribute name + dat_val : any + Value to be assigned to attribute specified by name + + """ + # Ensure the shape is correct + if np.asarray(dat_val).shape == () and self.dat_ind.shape != (): + dat_val = np.full(shape=self.dat_ind.shape, fill_value=dat_val) + + self.__setattr__(dat_name, dat_val) + return + + def _test_coords(self): + """Test the location and vector coordinate specifications. + + Raises + ------ + ValueError + If an unknown coordinate system is supplied or a mix of gedetic + and geocentric is supplied + + """ + good_coords = ['magnetic', 'geocentric', 'geodetic'] + + if self.loc_coord not in good_coords: + raise ValueError(''.join(['unknown location coordinate: ', + repr(self.loc_coord), ', expects one of ', + repr(good_coords)])) + + if self.vect_coord not in good_coords: + raise ValueError(''.join(['unknown vector coordinate: ', + repr(self.vect_coord), + ', expects one of ', repr(good_coords)])) + + if self.vect_coord != self.loc_coord and 'magnetic' not in [ + self.vect_coord, self.loc_coord]: + raise ValueError('incompatible vector and location coordinates') + + return + def _test_update_vector_shape(self): """Test and update the shape of the VectorData attributes. @@ -289,16 +425,15 @@ def _test_update_vector_shape(self): Notes ----- - Sets the `vshape` attribute and updates the shape of `aacgm_n`, - `aacgm_e`, and `aacgm_z` if needed + Sets the `vshape` attribute and updates the shape of `vect_n`, + `vect_e`, and `vect_z` if needed """ # Get the required input shapes vshapes = list() - for vshape in [self.aacgm_lat.shape, self.aacgm_mlt.shape, - self.dat_ind.shape, self.aacgm_n.shape, - self.aacgm_e.shape, self.aacgm_z.shape]: + for vshape in [self.lat.shape, self.lt.shape, self.dat_ind.shape, + self.vect_n.shape, self.vect_e.shape, self.vect_z.shape]: if vshape not in vshapes: vshapes.append(vshape) @@ -314,15 +449,12 @@ def _test_update_vector_shape(self): raise ValueError('data index shape must match vector shape') # Vector input needs to be the same length - if self.aacgm_n.shape == (): - self.aacgm_n = np.full(shape=self.vshape, - fill_value=self.aacgm_n) - if self.aacgm_e.shape == (): - self.aacgm_e = np.full(shape=self.vshape, - fill_value=self.aacgm_e) - if self.aacgm_z.shape == (): - self.aacgm_z = np.full(shape=self.vshape, - fill_value=self.aacgm_z) + if self.vect_n.shape == (): + self.vect_n = np.full(shape=self.vshape, fill_value=self.vect_n) + if self.vect_e.shape == (): + self.vect_e = np.full(shape=self.vshape, fill_value=self.vect_e) + if self.vect_z.shape == (): + self.vect_z = np.full(shape=self.vshape, fill_value=self.vect_z) return def _test_update_bound_shape(self): @@ -333,10 +465,6 @@ def _test_update_bound_shape(self): ValueError If mismatches in the attribute shapes are encountered - Notes - ----- - Updates the shape of `aacgm_n`, `aacgm_e`, and `aacgm_z` if needed - """ # Test the OCB input shape oshapes = list() @@ -363,28 +491,51 @@ def _test_update_bound_shape(self): raise ValueError('Mismatched OCB and Vector input shapes') return + def _assign_normal_coord_output(self, out_coord, ind=None): + """Get and assign OCB coordinates. + + Parameters + ---------- + out_coord : tuple + Tuple of outputs from `normal_coord` method + ind : int or NoneType + Index for assigning data to the local OCB attributes + + """ + # OCBoundary and EABoundary have thre outputs, DualBoundary has four + if len(out_coord) == 3: + if ind is None: + (self.ocb_lat, self.ocb_mlt, self.r_corr) = out_coord + else: + (self.ocb_lat[ind], self.ocb_mlt[ind], + self.r_corr[ind]) = out_coord + else: + if ind is None: + (self.ocb_lat, self.ocb_mlt, _, self.r_corr) = out_coord + else: + (self.ocb_lat[ind], self.ocb_mlt[ind], _, + self.r_corr[ind]) = out_coord + return + @property - def aacgm_mag(self): - """Magntiude of the AACGM vector(s).""" - return self._aacgm_mag + def vect_mag(self): + """Magntiude of the vector(s).""" + return self._vect_mag - @aacgm_mag.setter - def aacgm_mag(self, aacgm_mag): + @vect_mag.setter + def vect_mag(self, vect_mag): # Assign the vector magnitude(s) - aacgm_sqrt = np.sqrt(self.aacgm_n**2 + self.aacgm_e**2 - + self.aacgm_z**2) + vect_sqrt = np.sqrt(self.vect_n**2 + self.vect_e**2 + self.vect_z**2) - if np.all(np.isnan(aacgm_mag)): - self._aacgm_mag = aacgm_sqrt + if np.all(np.isnan(vect_mag)): + self._vect_mag = vect_sqrt else: - if np.any(np.greater(abs(aacgm_mag - aacgm_sqrt), 1.0e-3, - where=~np.isnan(aacgm_mag))): - ocbpy.logger.warning("".join(["inconsistent AACGM components ", - "with a maximum difference of ", - "{:} > 1.0e-3".format( - abs(aacgm_mag - - aacgm_sqrt).max())])) - self._aacgm_mag = aacgm_mag + if np.any(np.greater(abs(vect_mag - vect_sqrt), 1.0e-3, + where=~np.isnan(vect_mag))): + ocbpy.logger.warning("".join([ + "inconsistent vector components with a maximum difference ", + "of {:} > 1.0e-3".format(abs(vect_mag - vect_sqrt).max())])) + self._vect_mag = vect_mag return @property @@ -409,8 +560,8 @@ def dat_ind(self, dat_ind): # Reset the calculated boundary data self.clear_data() - # Re-calculate the AACGM magnitude - self.aacgm_mag = np.nan + # Re-calculate the vector magnitude + self.vect_mag = np.nan return @property @@ -483,6 +634,39 @@ def r_corr(self, r_corr): self._ocb_attr_setter('_r_corr', r_corr) return + @property + def lat(self): + """Vector latitude in degrees.""" + return self._lat + + @lat.setter + def lat(self, lat): + # Set the boundary latitude value and ensure the shape is correct + self._dat_attr_setter('_lat', lat) + return + + @property + def lt(self): + """Vector local time in hours.""" + return self._lt + + @lt.setter + def lt(self, lt): + # Set the vector LT value and ensure the shape is correct + self._dat_attr_setter('_lt', lt) + return + + @property + def height(self): + """Vector in km.""" + return self._height + + @height.setter + def height(self, height): + # Set the vector height value and ensure the shape is correct + self._dat_attr_setter('_height', height) + return + def clear_data(self): """Clear or initialize the output data attributes.""" @@ -496,6 +680,7 @@ def clear_data(self): self.ocb_quad = np.zeros(shape=self.vshape) self.vec_quad = np.zeros(shape=self.vshape) self.pole_angle = np.full(shape=self.vshape, fill_value=np.nan) + # TODO(#133): remove `aacgm_naz` self.aacgm_naz = np.full(shape=self.vshape, fill_value=np.nan) self.ocb_aacgm_lat = np.full(shape=self.vshape, fill_value=np.nan) self.ocb_aacgm_mlt = np.full(shape=self.vshape, fill_value=np.nan) @@ -509,48 +694,46 @@ def set_ocb(self, ocb, scale_func=None): ocb : ocbpy.OCBoundary or ocbpy.DualBoundary OCB, EAB, or Dual boundary object scale_func : function - Function for scaling AACGM magnitude with arguments: - [measurement value, mesurement AACGM latitude (degrees), - mesurement OCB latitude (degrees)] - Not necessary if defined earlier or no scaling is needed. - (default=None) + Function for scaling the vector magnitude with arguments: + measurement value, measurement latitude (degrees), and measurement + boundary-adjusted latitude (degrees). Not necessary if defined + earlier or no scaling is needed. (default=None) """ + # Update the data values to be in magnetic coordinates + dtime = ocb.dtime[ocb.rec_ind] if self.ocb_ind.shape == () else [ + ocb.dtime[ind] for ind in self.ocb_ind] + self.update_vect_coords_to_mag(dtime, ocb.hemisphere) # If the OCB vector coordinates weren't included in the initial info, # update them here if(np.all(np.isnan(self.ocb_lat)) or np.all(np.isnan(self.ocb_mlt)) or np.all(np.isnan(self.r_corr))): - # Because the OCB and AACGM magnetic field are both time dependent, - # can't call this function with multiple OCBs + # Because the boundary locations and magnetic field are both time + # dependent, we can't call this function with multiple OCB/EABs if self.ocb_ind.shape == (): - # Initialise the OCB index + # Initialise the boundary index ocb.rec_ind = self.ocb_ind # Calculate the coordinates and save the output - out_coord = ocb.normal_coord(self.aacgm_lat, self.aacgm_mlt) - - if len(out_coord) == 3: - (self.ocb_lat, self.ocb_mlt, self.r_corr) = out_coord - else: - (self.ocb_lat, self.ocb_mlt, _, self.r_corr) = out_coord + out_coord = ocb.normal_coord(self.lat, self.lt, + coords=self.loc_coord, + height=self.height) + self._assign_normal_coord_output(out_coord) else: - # Cycle through the OCB indices + # Cycle through the boundary indices for i, ocb.rec_ind in enumerate(self.ocb_ind): - # Calcualte the coordinates and save the output + # Handle time different, depending on the OCB and + # data shapes. Calcualte the coordinates and save the output if self.ocb_ind.shape == self.dat_ind.shape: - out_coord = ocb.normal_coord(self.aacgm_lat[i], - self.aacgm_mlt[i]) + out_coord = ocb.normal_coord(self.lat[i], self.lt[i], + coords=self.loc_coord, + height=self.height[i]) else: - out_coord = ocb.normal_coord(self.aacgm_lat, - self.aacgm_mlt) - - if len(out_coord) == 3: - (self.ocb_lat[i], self.ocb_mlt[i], - self.r_corr[i]) = out_coord - else: - (self.ocb_lat[i], self.ocb_mlt[i], _, - self.r_corr[i]) = out_coord + out_coord = ocb.normal_coord(self.lat, self.lt, + coords=self.loc_coord, + height=self.height) + self._assign_normal_coord_output(out_coord, i) # Exit if the OCB coordinates can't be calculated at this location if(np.all(np.isnan(self.ocb_lat)) or np.all(np.isnan(self.ocb_mlt)) @@ -564,16 +747,14 @@ def set_ocb(self, ocb, scale_func=None): self.scaled_r = np.full( shape=self.unscaled_r.shape, fill_value=(90.0 - abs(ocb.ocb.boundary_lat))) - self.ocb_aacgm_mlt = ocbpy.ocb_time.deg2hr( - ocb.ocb.phi_cent[iocb]) - self.ocb_aacgm_lat = 90.0 - ocb.ocb.r_cent[iocb] + self.ocb_aacgm_mlt, self.ocb_aacgm_lat = vectors.get_pole_loc( + ocb.ocb.phi_cent[iocb], ocb.ocb.r_cent[iocb]) else: self.unscaled_r = ocb.r[self.ocb_ind] + self.r_corr self.scaled_r = np.full(shape=self.unscaled_r.shape, fill_value=(90.0 - abs(ocb.boundary_lat))) - self.ocb_aacgm_mlt = ocbpy.ocb_time.deg2hr( - ocb.phi_cent[self.ocb_ind]) - self.ocb_aacgm_lat = 90.0 - ocb.r_cent[self.ocb_ind] + self.ocb_aacgm_mlt, self.ocb_aacgm_lat = vectors.get_pole_loc( + ocb.phi_cent[self.ocb_ind], ocb.r_cent[self.ocb_ind]) # Get the angle at the data vector appended by the AACGM and OCB poles self.calc_vec_pole_angle() @@ -606,111 +787,37 @@ def define_quadrants(self): the data vector location, assuming vertical is positive downwards Quadrants: 1 [N, E]; 2 [N, W]; 3 [S, W]; 4 [S, E] - Requires `ocb_aacgm_mlt`, `aacgm_mlt`, and `pole_angle`. - Updates `ocb_quad` and `vec_quad` + Requires `ocb_aacgm_mlt`, `lt`, `pole_angle`, `vect_n`, and `vect_e`. + Both `loc_coord` and `vect_coord` must be 'magnetic'. Updates `ocb_quad` + and `vec_quad` Raises ------ ValueError - If the required input is undefined + If the required input is undefined or incorrect """ + # When defining quadrants, we will need the vector information in + # magnetic coordinates + if self.loc_coord != "magnetic" or self.vect_coord != "magnetic": + raise ValueError('need magnetic coordinates to define quadrants') - # Cast the input as arrays - self.ocb_aacgm_mlt = np.asarray(self.ocb_aacgm_mlt) - self.aacgm_mlt = np.asarray(self.aacgm_mlt) - self.pole_angle = np.asarray(self.pole_angle) - - # Test input + # Test input, where it is allowable to have empty vector input if np.all(np.isnan(self.ocb_aacgm_mlt)): raise ValueError("OCB pole location required") - if np.all(np.isnan(self.aacgm_mlt)): - raise ValueError("Vector AACGM location required") + if np.all(np.isnan(self.lt)): + raise ValueError("Vector location required") if np.all(np.isnan(self.pole_angle)): raise ValueError("vector angle in poles-vector triangle required") # Determine where the OCB pole is relative to the data vector - ocb_adj_mlt = self.ocb_aacgm_mlt - self.aacgm_mlt - - neg_mask = (np.less(ocb_adj_mlt, 0.0, where=~np.isnan(ocb_adj_mlt)) - & ~np.isnan(ocb_adj_mlt)) - while np.any(neg_mask): - if ocb_adj_mlt.shape == (): - ocb_adj_mlt += 24.0 - neg_mask = [False] - else: - ocb_adj_mlt[neg_mask] += 24.0 - neg_mask = (np.less(ocb_adj_mlt, 0.0, - where=~np.isnan(ocb_adj_mlt)) - & ~np.isnan(ocb_adj_mlt)) - - large_mask = (np.greater_equal(abs(ocb_adj_mlt), 24.0, - where=~np.isnan(ocb_adj_mlt)) - & ~np.isnan(ocb_adj_mlt)) - if np.any(large_mask): - if ocb_adj_mlt.shape == (): - ocb_adj_mlt -= 24.0 * np.sign(ocb_adj_mlt) - else: - ocb_adj_mlt[large_mask] -= 24.0 * np.sign( - ocb_adj_mlt[large_mask]) - - # Find the quadrant in which the OCB pole lies - nan_mask = (~np.isnan(self.pole_angle) & ~np.isnan(ocb_adj_mlt)) - quad1_mask = (np.less(self.pole_angle, 90.0, where=nan_mask) - & np.less(ocb_adj_mlt, 12.0, where=nan_mask) & nan_mask) - quad2_mask = (np.less(self.pole_angle, 90.0, where=nan_mask) - & np.greater_equal(ocb_adj_mlt, 12.0, where=nan_mask) - & nan_mask) - quad3_mask = (np.greater_equal(self.pole_angle, 90.0, where=nan_mask) - & np.greater_equal(ocb_adj_mlt, 12.0, where=nan_mask) - & nan_mask) - quad4_mask = (np.greater_equal(self.pole_angle, 90.0, where=nan_mask) - & np.less(ocb_adj_mlt, 12.0, where=nan_mask) & nan_mask) - - if self.ocb_quad.shape == (): - if np.all(quad1_mask): - self.ocb_quad = np.asarray(1) - elif np.all(quad2_mask): - self.ocb_quad = np.asarray(2) - elif np.all(quad3_mask): - self.ocb_quad = np.asarray(3) - elif np.all(quad4_mask): - self.ocb_quad = np.asarray(4) - else: - self.ocb_quad[quad1_mask] = 1 - self.ocb_quad[quad2_mask] = 2 - self.ocb_quad[quad3_mask] = 3 - self.ocb_quad[quad4_mask] = 4 + self.ocb_quad = vectors.define_pole_quadrants( + self.lt, self.ocb_aacgm_mlt, self.pole_angle) # Now determine which quadrant the vector is pointed into - nan_mask = (~np.isnan(self.aacgm_n) & ~np.isnan(self.aacgm_e)) - quad1_mask = (np.greater_equal(self.aacgm_n, 0.0, where=nan_mask) - & np.greater_equal(self.aacgm_e, 0.0, where=nan_mask) - & nan_mask) - quad2_mask = (np.greater_equal(self.aacgm_n, 0.0, where=nan_mask) - & np.less(self.aacgm_e, 0.0, where=nan_mask) & nan_mask) - quad3_mask = (np.less(self.aacgm_n, 0.0, where=nan_mask) - & np.less(self.aacgm_e, 0.0, where=nan_mask) & nan_mask) - quad4_mask = (np.less(self.aacgm_n, 0.0, where=nan_mask) - & np.greater_equal(self.aacgm_e, 0.0, where=nan_mask) - & nan_mask) - - if self.vec_quad.shape == (): - if np.all(quad1_mask): - self.vec_quad = np.asarray(1) - elif np.all(quad2_mask): - self.vec_quad = np.asarray(2) - elif np.all(quad3_mask): - self.vec_quad = np.asarray(3) - elif np.all(quad4_mask): - self.vec_quad = np.asarray(4) - else: - self.vec_quad[quad1_mask] = 1 - self.vec_quad[quad2_mask] = 2 - self.vec_quad[quad3_mask] = 3 - self.vec_quad[quad4_mask] = 4 + self.vec_quad = vectors.define_vect_quadrants(self.vect_n, self.vect_e) return @@ -724,25 +831,15 @@ def scale_vector(self): Notes ----- - Requires `ocb_lat`, `ocb_mlt`, `ocb_aacgm_mlt`, and `pole_angle`. - Updates `ocb_n`, `ocb_e`, `ocb_z`, and `ocb_mag` + Requires `lat`, `lt`, `ocb_aacgm_mlt`, `ocb_aacgm_lat`, and + `pole_angle`. Updates `ocb_n`, `ocb_e`, `ocb_z`, and `ocb_mag`. + Temporarily updates `aacgm_naz`, which has been deprecated and will + be removed in version 0.4.1+. """ - - # Ensure the input is array-like - self.ocb_lat = np.asarray(self.ocb_lat) - self.ocb_mlt = np.asarray(self.ocb_mlt) - self.ocb_aacgm_mlt = np.asarray(self.ocb_aacgm_mlt) - self.pole_angle = np.asarray(self.pole_angle) - self.aacgm_n = np.asarray(self.aacgm_n) - self.aacgm_e = np.asarray(self.aacgm_e) - self.aacgm_z = np.asarray(self.aacgm_z) - self.ocb_quad = np.asarray(self.ocb_quad) - self.vec_quad = np.asarray(self.vec_quad) - # Test input - if np.all(np.isnan(self.ocb_lat)) or np.all(np.isnan(self.ocb_mlt)): - raise ValueError("OCB coordinates required") + if np.all(np.isnan(self.lat)) or np.all(np.isnan(self.lt)): + raise ValueError("Vector locations required") if np.all(np.isnan(self.ocb_aacgm_mlt)): raise ValueError("OCB pole location required") @@ -750,154 +847,45 @@ def scale_vector(self): if np.all(np.isnan(self.pole_angle)): raise ValueError("vector angle in poles-vector triangle required") - # Determine the special case assignments - zero_mask = ((self.aacgm_n == 0.0) & (self.aacgm_e == 0.0)) - ns_mask = ((self.pole_angle == 0.0) | (self.pole_angle == 180.0)) - norm_mask = ~(zero_mask + ns_mask) - - # There's no magnitude, so nothing to adjust - if np.any(zero_mask): - if self.aacgm_n.shape == (): - self.ocb_n = np.zeros(shape=self.ocb_n.shape) - self.ocb_e = np.zeros(shape=self.ocb_e.shape) - self.ocb_z = np.zeros(shape=self.ocb_z.shape) - else: - self.ocb_n[zero_mask] = 0.0 - self.ocb_e[zero_mask] = 0.0 - self.ocb_z[zero_mask] = 0.0 - - # The measurement is aligned with the AACGM and OCB poles - if np.any(ns_mask): - if self.scale_func is None: - if self.aacgm_n.shape == (): - self.ocb_n = np.full(shape=self.ocb_n.shape, - fill_value=self.aacgm_n) - self.ocb_e = np.full(shape=self.ocb_e.shape, - fill_value=self.aacgm_e) - self.ocb_z = np.full(shape=self.ocb_z.shape, - fill_value=self.aacgm_z) - else: - self.ocb_n[ns_mask] = self.aacgm_n[ns_mask] - self.ocb_e[ns_mask] = self.aacgm_e[ns_mask] - self.ocb_z[ns_mask] = self.aacgm_z[ns_mask] - else: - if self.aacgm_n.shape == (): - self.ocb_n = np.full(shape=self.ocb_n.shape, - fill_value=self.scale_func( - self.aacgm_n, self.unscaled_r, - self.scaled_r)) - self.ocb_e = np.full(shape=self.ocb_e.shape, - fill_value=self.scale_func( - self.aacgm_e, self.unscaled_r, - self.scaled_r)) - self.ocb_z = np.full(shape=self.ocb_z.shape, - fill_value=self.scale_func( - self.aacgm_z, self.unscaled_r, - self.scaled_r)) - else: - self.ocb_n[ns_mask] = self.scale_func( - self.aacgm_n[ns_mask], self.unscaled_r[ns_mask], - self.scaled_r[ns_mask]) - self.ocb_e[ns_mask] = self.scale_func( - self.aacgm_e[ns_mask], self.unscaled_r[ns_mask], - self.scaled_r[ns_mask]) - self.ocb_z[ns_mask] = self.scale_func( - self.aacgm_z[ns_mask], self.unscaled_r[ns_mask], - self.scaled_r[ns_mask]) - - # Determine if the measurement is on or between the poles - # This does not affect the vertical direction - sign_mask = ((self.pole_angle == 0.0) - & np.greater_equal(self.aacgm_lat, self.ocb_aacgm_lat, - where=~np.isnan(self.aacgm_lat)) - & ~np.isnan(self.aacgm_lat)) - if np.any(sign_mask): - if self.ocb_n.shape == (): - self.ocb_n *= -1.0 - self.ocb_e *= -1.0 - else: - self.ocb_n[sign_mask] *= -1.0 - self.ocb_e[sign_mask] *= -1.0 - - # If there are still undefined vectors, assign them using the - # typical case - if np.any(norm_mask): - # If not defined, get the OCB and vector quadrants - if(np.any(self.ocb_quad[norm_mask] == 0) - or np.any(self.vec_quad[norm_mask] == 0)): - self.define_quadrants() - - # Get the unscaled 2D vector magnitude and - # calculate the AACGM north azimuth in degrees - if self.aacgm_n.shape == (): - vmag = np.sqrt(self.aacgm_n**2 + self.aacgm_e**2) - self.aacgm_naz = np.degrees(np.arccos(self.aacgm_n / vmag)) - else: - vmag = np.sqrt(self.aacgm_n[norm_mask]**2 - + self.aacgm_e[norm_mask]**2) - self.aacgm_naz[norm_mask] = np.degrees( - np.arccos(self.aacgm_n[norm_mask] / vmag)) - - # Get the OCB north azimuth in radians - ocb_angle = np.radians(self.calc_ocb_polar_angle()) - - # Get the sign of the North and East components - vsigns = self.calc_ocb_vec_sign(north=True, east=True) - - # Scale the vector along the OCB north and account for - # any changes associated with adjusting the size of the polar cap - if self.scale_func is not None: - if self.unscaled_r.shape == (): - un_r = self.unscaled_r - sc_r = self.scaled_r - else: - un_r = self.unscaled_r[norm_mask] - sc_r = self.scaled_r[norm_mask] - - if self.aacgm_z.shape == (): - a_z = self.aacgm_z - else: - a_z = self.aacgm_z[norm_mask] + # Adjust the vector to OCB coordinates without scaling + self.ocb_n, self.ocb_e, self.ocb_z = vectors.adjust_vector( + self.lt, self.lat, self.vect_n, self.vect_e, self.vect_z, + self.vec_quad, self.ocb_aacgm_mlt, self.ocb_aacgm_lat, + self.pole_angle, self.ocb_quad) - vmag = self.scale_func(vmag, un_r, sc_r) - vz = self.scale_func(a_z, un_r, sc_r) - else: - if self.aacgm_z.shape == (): - vz = self.aacgm_z - else: - vz = self.aacgm_z[norm_mask] - nan_mask = (np.isnan(vmag) - | (np.isnan(ocb_angle) if ocb_angle.shape == () - else np.isnan(ocb_angle[norm_mask]))) - vz[nan_mask] = np.nan - - # Restrict the OCB angle to result in positive sines and cosines - lmask = ocb_angle > np.pi / 2.0 - if np.any(lmask): - if ocb_angle.shape == (): - ocb_angle = np.pi - ocb_angle - else: - ocb_angle[lmask] = np.pi - ocb_angle[lmask] - - # Calculate the vector components - if vmag.shape == (): - self.ocb_n = np.full(shape=self.ocb_n.shape, - fill_value=(vsigns['north'] * vmag - * np.cos(ocb_angle))) - self.ocb_e = np.full(shape=self.ocb_e.shape, - fill_value=(vsigns['east'] * vmag - * np.sin(ocb_angle))) - self.ocb_z = np.full(shape=self.ocb_z.shape, fill_value=vz) + # TODO(#133): remove `aacgm_naz` + vmag = np.sqrt(self.vect_n**2 + self.vect_e**2) + if len(vmag.shape) == 0: + self.aacgm_naz = np.degrees(np.arccos(self.vect_n / vmag)) + else: + zero_mask = ((self.vect_n == 0.0) & (self.vect_e == 0.0)) + ns_mask = ((self.pole_angle == 0.0) | (self.pole_angle == 180.0)) + norm_mask = ~(zero_mask + ns_mask) + self.aacgm_naz[norm_mask] = np.degrees(np.arccos( + self.vect_n[norm_mask] / vmag[norm_mask])) + + # Scale the outputs, if desired + if self.scale_func is not None: + if len(self.ocb_n.shape) == 0: + self.ocb_n = np.full( + shape=self.ocb_n.shape, fill_value=self.scale_func( + self.vect_n, self.unscaled_r, self.scaled_r)) + self.ocb_e = np.full( + shape=self.ocb_e.shape, fill_value=self.scale_func( + self.vect_e, self.unscaled_r, self.scaled_r)) + self.ocb_z = np.full( + shape=self.ocb_z.shape, fill_value=self.scale_func( + self.vect_z, self.unscaled_r, self.scaled_r)) else: - self.ocb_n[norm_mask] = (vsigns['north'][norm_mask] * vmag - * np.cos(ocb_angle[norm_mask])) - self.ocb_e[norm_mask] = (vsigns['east'][norm_mask] * vmag - * np.sin(ocb_angle[norm_mask])) - self.ocb_z[norm_mask] = vz + self.ocb_n = self.scale_func(self.vect_n, self.unscaled_r, + self.scaled_r) + self.ocb_e = self.scale_func(self.vect_e, self.unscaled_r, + self.scaled_r) + self.ocb_z = self.scale_func(self.vect_z, self.unscaled_r, + self.scaled_r) # Calculate the scaled OCB vector magnitude - self.ocb_mag = np.sqrt(self.ocb_n**2 + self.ocb_e**2 - + self.ocb_z**2) + self.ocb_mag = np.sqrt(self.ocb_n**2 + self.ocb_e**2 + self.ocb_z**2) return @@ -919,82 +907,26 @@ def calc_ocb_polar_angle(self): Requires `ocb_quad`, `vec_quad`, `aacgm_naz`, and `pole_angle` """ - - quad_range = np.arange(1, 5) - + # TODO(#133): deprecation warning, method is no longer needed here + warnings.warn("".join(["`calc_ocb_polar_angle` method deprecated, and", + " will be removed in version 0.4.1+. Instead, ", + "use `ocbpy.vectors.calc_dest_polar_angle`."]), + DeprecationWarning, stacklevel=2) + # Test input - if not np.any(np.isin(self.ocb_quad, quad_range)): - raise ValueError("OCB quadrant undefined") - - if not np.any(np.isin(self.vec_quad, quad_range)): - raise ValueError("Vector quadrant undefined") - if np.all(np.isnan(self.aacgm_naz)): - raise ValueError("AACGM polar angle undefined") + raise ValueError("AACGM North polar angle undefined") if np.all(np.isnan(self.pole_angle)): raise ValueError("Vector angle undefined") - # Initialise the output and set the quadrant dictionary - nan_mask = (~np.isnan(self.aacgm_naz) & ~np.isnan(self.pole_angle)) - ocb_naz = np.full(shape=(self.aacgm_naz + self.pole_angle).shape, - fill_value=np.nan) - quads = {oquad: {vquad: (self.ocb_quad == oquad) - & (self.vec_quad == vquad) & nan_mask - for vquad in quad_range} for oquad in quad_range} - - # Create masks for the different quadrant combinations - abs_mask = (quads[1][1] | quads[2][2] | quads[3][3] | quads[4][4]) - add_mask = (quads[1][2] | quads[1][3] | quads[2][1] | quads[2][4] - | quads[3][1] | quads[4][2]) - mpa_mask = (quads[1][4] | quads[2][3]) - maa_mask = (quads[3][2] | quads[4][1]) - cir_mask = (quads[3][4] | quads[4][3]) - - # Calculate OCB polar angle based on the quadrants and other angles - if np.any(abs_mask): - if ocb_naz.shape == (): - ocb_naz = abs(self.aacgm_naz - self.pole_angle) - else: - ocb_naz[abs_mask] = abs(self.aacgm_naz - - self.pole_angle)[abs_mask] - - if np.any(add_mask): - if ocb_naz.shape == (): - ocb_naz = self.pole_angle + self.aacgm_naz - if ocb_naz > 180.0: - ocb_naz = 360.0 - ocb_naz - else: - ocb_naz[add_mask] = (self.pole_angle - + self.aacgm_naz)[add_mask] - lmask = (ocb_naz > 180.0) & add_mask - if np.any(lmask): - ocb_naz[lmask] = 360.0 - ocb_naz[lmask] - - if np.any(mpa_mask): - if ocb_naz.shape == (): - ocb_naz = self.aacgm_naz - self.pole_angle - else: - ocb_naz[mpa_mask] = (self.aacgm_naz - - self.pole_angle)[mpa_mask] - - if np.any(maa_mask): - if ocb_naz.shape == (): - ocb_naz = self.pole_angle - self.aacgm_naz - else: - ocb_naz[maa_mask] = (self.pole_angle - - self.aacgm_naz)[maa_mask] - - if np.any(cir_mask): - if ocb_naz.shape == (): - ocb_naz = 360.0 - self.aacgm_naz - self.pole_angle - else: - ocb_naz[cir_mask] = (360.0 - self.aacgm_naz - - self.pole_angle)[cir_mask] + # Calcuate the North azimuth angle for the OCB pole + ocb_naz = vectors.calc_dest_polar_angle( + self.ocb_quad, self.vec_quad, self.aacgm_naz, self.pole_angle) return ocb_naz - def calc_ocb_vec_sign(self, north=False, east=False, quads=dict()): + def calc_ocb_vec_sign(self, north=False, east=False, quads=None): """Calculate the sign of the North and East components. Parameters @@ -1003,9 +935,9 @@ def calc_ocb_vec_sign(self, north=False, east=False, quads=dict()): Get the sign of the north component(s) (default=False) east : bool Get the sign of the east component(s) (default=False) - quads : dict + quads : dict or NoneType Dictionary of boolean values or arrays of boolean values for OCB - and Vector quadrants. (default=dict()) + and Vector quadrants. (default=None) Returns ------- @@ -1020,104 +952,30 @@ def calc_ocb_vec_sign(self, north=False, east=False, quads=dict()): Notes ----- - Requires `ocb_quad`, `vec_quad`, `aacgm_naz`, and `pole_angle` + Requires `ocb_quad`, `vec_quad`, `aacgm_naz`, and `pole_angle`. + Method is deprecated and will be removed in version 0.4.1+. """ - - quad_range = np.arange(1, 5) - - # Ensure the required input is array-like - self.ocb_quad = np.asarray(self.ocb_quad) - self.vec_quad = np.asarray(self.vec_quad) - self.aacgm_naz = np.asarray(self.aacgm_naz) - self.pole_angle = np.asarray(self.pole_angle) + # TODO(#133): deprecation warning, method is no longer needed here + warnings.warn("".join(["`calc_ocb_vec_sign` method deprecated, and", + " will be removed in version 0.4.1+. Instead, ", + "use `ocbpy.vectors.calc_dest_vec_sign`."]), + DeprecationWarning, stacklevel=2) # Test input if not np.any([north, east]): raise ValueError("must set at least one direction") - if not np.any(np.isin(self.ocb_quad, quad_range)): - raise ValueError("OCB quadrant undefined") - - if not np.any(np.isin(self.vec_quad, quad_range)): - raise ValueError("Vector quadrant undefined") - if np.all(np.isnan(self.aacgm_naz)): raise ValueError("AACGM polar angle undefined") if np.all(np.isnan(self.pole_angle)): raise ValueError("Vector angle undefined") - # If necessary, initialise quadrant dictionary - nan_mask = (~np.isnan(self.aacgm_naz) & ~np.isnan(self.pole_angle)) - if not np.all([kk in quads.keys() for kk in quad_range]): - quads = {o: {v: (self.ocb_quad == o) & (self.vec_quad == v) - & nan_mask for v in quad_range} for o in quad_range} - - # Initialise output - vsigns = {"north": np.zeros(shape=quads[1][1].shape), - "east": np.zeros(shape=quads[1][1].shape)} - - # Determine the desired vector signs - if north: - pole_minus = self.pole_angle - 90.0 - minus_pole = 90.0 - self.pole_angle - pole_plus = self.pole_angle + 90.0 - - pmask = (quads[1][1] | quads[2][2] | quads[3][3] | quads[4][4] - | ((quads[1][4] | quads[2][3]) - & np.less_equal(self.aacgm_naz, pole_plus, - where=nan_mask)) - | ((quads[1][2] | quads[2][1]) - & np.less_equal(self.aacgm_naz, minus_pole, - where=nan_mask)) - | ((quads[3][4] | quads[4][3]) - & np.greater_equal(self.aacgm_naz, 180.0 - pole_minus, - where=nan_mask)) - | ((quads[3][2] | quads[4][1]) - & np.greater_equal(self.aacgm_naz, pole_minus, - where=nan_mask))) - - if np.any(pmask): - if vsigns["north"].shape == (): - vsigns["north"] = 1 - else: - vsigns["north"][pmask] = 1 - - if np.any(~pmask): - if vsigns["north"].shape == (): - vsigns["north"] = -1 - else: - vsigns["north"][~pmask] = -1 - - if east: - minus_pole = 180.0 - self.pole_angle - - pmask = (quads[1][4] | quads[2][1] | quads[3][2] | quads[4][3] - | ((quads[1][1] | quads[4][4]) - & np.greater_equal(self.aacgm_naz, self.pole_angle, - where=nan_mask)) - | ((quads[3][1] | quads[2][4]) - & np.less_equal(self.aacgm_naz, minus_pole, - where=nan_mask)) - | ((quads[4][2] | quads[1][3]) - & np.greater_equal(self.aacgm_naz, minus_pole, - where=nan_mask)) - | ((quads[2][2] | quads[3][3]) - & np.less_equal(self.aacgm_naz, self.pole_angle, - where=nan_mask))) - - if np.any(pmask): - if vsigns["east"].shape == (): - vsigns["east"] = 1 - else: - vsigns["east"][pmask] = 1 - - if np.any(~pmask): - if vsigns["east"].shape == (): - vsigns["east"] = -1 - else: - vsigns["east"][~pmask] = -1 + # Calcualte the sign of the North and East vector components + vsigns = vectors.calc_dest_vec_sign( + self.ocb_quad, self.vec_quad, self.aacgm_naz, self.pole_angle, + north=north, east=east, quads=quads) return vsigns @@ -1127,27 +985,32 @@ def calc_vec_pole_angle(self): Raises ------ ValueError - If the input is undefined or inappropriately sized arrays + If the input is undefined or inappropriate Notes ----- - Requires `aacgm_mlt`, `aacgm_lat`, `ocb_aacgm_mlt`, and `ocb_aacgm_lat`. - Updates `pole_angle` using spherical trigonometry. + Requires `lt` and `lat` in magnetic coordinates, as well as + defined `ocb_aacgm_mlt` and `ocb_aacgm_lat` attributes. Updates + `pole_angle` using spherical trigonometry. """ + # When defining vector-pole angles, we will need the vector location in + # magnetic coordinates + if self.loc_coord != "magnetic": + raise ValueError('need magnetic coordinates to define quadrants') # Cast inputs as arrays - self.aacgm_mlt = np.asarray(self.aacgm_mlt) - self.aacgm_lat = np.asarray(self.aacgm_lat) + self.lt = np.asarray(self.lt) + self.lat = np.asarray(self.lat) self.ocb_aacgm_mlt = np.asarray(self.ocb_aacgm_mlt) self.ocb_aacgm_lat = np.asarray(self.ocb_aacgm_lat) # Test input - if np.all(np.isnan(self.aacgm_mlt)): - raise ValueError("AACGM MLT of Vector(s) undefinded") + if np.all(np.isnan(self.lt)): + raise ValueError("Vector local time is undefined") - if np.all(np.isnan(self.aacgm_lat)): - raise ValueError("AACGM latitude of Vector(s) undefined") + if np.all(np.isnan(self.lat)): + raise ValueError("Vector latitude is undefined") if np.all(np.isnan(self.ocb_aacgm_mlt)): raise ValueError("AACGM MLT of OCB pole(s) undefined") @@ -1155,78 +1018,242 @@ def calc_vec_pole_angle(self): if np.all(np.isnan(self.ocb_aacgm_lat)): raise ValueError("AACGM latitude of OCB pole(s) undefined") - # Convert the AACGM MLT of the observation and OCB pole to radians, - # then calculate the difference between them. - del_long = ocbpy.ocb_time.hr2rad(self.ocb_aacgm_mlt - self.aacgm_mlt) + # Find the angle between the AACGM pole, the vector location in AACGM + # coordinates, and the high-latitude boundary pole + self.pole_angle = vectors.calc_vec_pole_angle( + self.lt, self.lat, self.ocb_aacgm_mlt, self.ocb_aacgm_lat) - if del_long.shape == (): - if del_long < -np.pi: - del_long += 2.0 * np.pi - else: - del_long[del_long < -np.pi] += 2.0 * np.pi + return - # Initalize the output - self.pole_angle = np.full(shape=del_long.shape, fill_value=np.nan) + def update_loc_coords(self, dtimes, coord='magnetic'): + """Update location coordiantes to the desired system. - # Assign the extreme values - if del_long.shape == (): - if del_long in [-np.pi, 0.0, np.pi]: - if abs(self.aacgm_lat) > abs(self.ocb_aacgm_lat): - self.pole_angle = 180.0 + Parameters + ---------- + dtimes : dt.datetime or list-like + Datetime or list of datetimes for conversion + coord : str + Desired coordinate system, accepts 'magnetic', 'geodetic', and + 'geocentric' (default='magnetic') + + Raises + ------ + ValueError + If the time and location inputs are mismatched. + + Notes + ----- + Updates `lat`, `lt`, and `loc_coord` attributes. + + """ + dtime = None + + if coord.lower() != self.loc_coord: + # Ensure the data is shaped correctly + if len(self.lt.shape) == 0 and len(self.lat.shape) == 0: + if hasattr(dtimes, 'year'): + # There is only one time and one location + dtime = dtimes else: - self.pole_angle = 0.0 - return - else: - zero_mask = (((del_long == 0) | (abs(del_long) == np.pi)) - & np.greater(abs(self.aacgm_lat), - abs(self.ocb_aacgm_lat), - where=~np.isnan(del_long))) - flat_mask = (((del_long == 0) | (abs(del_long) == np.pi)) - & np.less_equal(abs(self.aacgm_lat), - abs(self.ocb_aacgm_lat), - where=~np.isnan(del_long))) - - self.pole_angle[flat_mask] = 180.0 - self.pole_angle[zero_mask] = 0.0 - update_mask = (~zero_mask & ~flat_mask) - - if not np.any(update_mask): + # There are multiple times and one location + self.lt = np.full(shape=len(dtimes), fill_value=self.lt) + self.lat = np.full(shape=len(dtimes), fill_value=self.lat) + else: + if hasattr(dtimes, 'year'): + # There is one time and multiple locations + dtime = dtimes + else: + # There are multiple times and locations, the length must + # be the same + if len(dtimes) != len(self.lt) or len(dtimes) != len( + self.lat): + raise ValueError('mismatched time and location inputs') + + # Initalize the AACGM method using the recommending tracing + methods = ["ALLOWTRACE"] + + # Handle the conversion to/from magnetic coordinates separately + if coord.lower() == "magnetic": + # Update the method + if self.loc_coord == "geocentric": + methods.append(self.loc_coord.upper()) + methods.append("G2A") + + if dtime is None: + new_lat = list() + new_lt = list() + method = "|".join(methods) + for i, val in enumerate(dtimes): + # Get the longitude for this time + lon = ocb_time.slt2glon(self.lt[i], val) + + # Convert to magnetic coordinates + out = aacgmv2.get_aacgm_coord_arr( + self.lat[i], lon, self.height[i], val, method) + + # Save the output + new_lat.append(out[0]) + new_lat.append(out[2]) + else: + # Get the longitude + lon = ocb_time.slt2glon(self.lt, dtime) + + # Convert to magnetic coordinates + new_lat, _, new_lt = aacgmv2.get_aacgm_coord_arr( + self.lat, lon, self.height, dtime, "|".join(methods)) + else: + # Update the method + if coord.lower() == "geocentric": + methods.append(coord.upper()) + methods.append("A2G") + + if dtime is None: + new_lat = list() + new_lt = list() + method = "|".join(methods) + for i, val in enumerate(dtimes): + # Get the longitude for this time + lon = aacgmv2.convert_mlt(self.lt[i], val, m2a=True) + + # Convert latitude and longitude + out = aacgmv2.convert_latlon_arr( + self.lat[i], lon, self.height[i], val, method) + + # Convert to SLT and save the latitude + new_lt.append(ocb_time.glon2slt(out[1], val)) + new_lat.append(out[0]) + else: + # Get the longitude + lon = aacgmv2.convert_mlt(self.lt, dtime, m2a=True) + + # Convert latitude and longitude + new_lat, new_lon, _ = aacgmv2.convert_latlon_arr( + self.lat, lon, self.height, dtime, "|".join(methods)) + + # Convert to SLT + new_lt = ocb_time.glon2slt(new_lon, dtime) + + # Update the location attributes + self.lat = np.asarray(new_lat) + self.lt = np.asarray(new_lt) + self.loc_coord = coord + + return + + def update_vect_coords_to_mag(self, dtimes, hemisphere): + """Convert geographic vector components into AAGGMV2 coordinates. + + Parameters + ---------- + dtimes : dt.datetime or list-like + Datetime or list of datetimes for conversion + hemisphere : int + -1 for Southern, 1 for Northern + + Notes + ----- + This follows the procedure in `set_ocb`, and is complicated to reverse. + + """ + dtime = None + + if self.vect_coord != "magnetic": + # Need the geographic and magnetic locations + if self.loc_coord == 'magnetic': + # Assign the magnetic location + mag_lt = np.asarray(self.lt) + mag_lat = np.asarray(self.lat) + + # Calculate the geographic location + self.update_loc_coords(dtimes, coord=self.vect_coord) + geo_lt = np.asarray(self.lt) + geo_lat = np.asarray(self.lat) + + # Re-assign the location values + self.lt = np.asarray(mag_lt) + self.lat = np.asarray(mag_lat) + self.loc_coord = 'magnetic' + else: + # Assign the geographic location + geo_lt = np.asarray(self.lt) + geo_lat = np.asarray(self.lat) + + # Update the location coordiantes to be magnetic + self.update_loc_coords(dtimes) + mag_lt = np.asarray(self.lt) + mag_lat = np.asarray(self.lat) + + # Exit if the magnetic coordinates can't be calculated + if np.all(np.isnan(mag_lat)) or np.all(np.isnan(mag_lt)): return - # Find the distance in radians between the two poles - hemisphere = np.sign(self.ocb_aacgm_lat) - rad_pole = hemisphere * np.pi * 0.5 - del_pole = hemisphere * (rad_pole - np.radians(self.ocb_aacgm_lat)) - - # Get the distance in radians between the AACGM pole and the data point - del_vect = hemisphere * (rad_pole - np.radians(self.aacgm_lat)) - - # Use the Vincenty formula for a sphere - del_ocb = np.arctan2(np.sqrt((np.cos(np.radians(self.ocb_aacgm_lat)) - * np.sin(del_long))**2 - + (np.cos(np.radians(self.aacgm_lat)) - * np.sin( - np.radians(self.ocb_aacgm_lat)) - - np.sin(np.radians(self.aacgm_lat)) - * np.cos( - np.radians(self.ocb_aacgm_lat)) - * np.cos(del_long))**2), - np.sin(np.radians(self.aacgm_lat)) - * np.sin(np.radians(self.ocb_aacgm_lat)) - + np.cos(np.radians(self.aacgm_lat)) - * np.cos(np.radians(self.ocb_aacgm_lat)) - * np.cos(del_long)) - - # Use the half-angle formula to get the pole angle - sum_sides = 0.5 * (del_vect + del_ocb + del_pole) - half_angle = np.sqrt(np.sin(sum_sides) * np.sin(sum_sides - del_pole) - / (np.sin(del_vect) * np.sin(del_ocb))) - - if self.pole_angle.shape == (): - self.pole_angle = np.degrees(2.0 * np.arccos(half_angle)) - else: - self.pole_angle[update_mask] = np.degrees( - 2.0 * np.arccos(half_angle[update_mask])) + # Ensure the geographic and magnetic coordinates are the same shape + if geo_lt.shape != mag_lt.shape: + if len(geo_lt.shape) == 0: + # The geographic values are singlular, expand them + geo_lt = np.full(shape=mag_lt.shape, fill_value=geo_lt) + geo_lat = np.full(shape=mag_lt.shape, fill_value=geo_lat) + elif len(mag_lt.shape) == 0: + # The magnetic values are singular, expend them + mag_lt = np.full(shape=geo_lt.shape, fill_value=mag_lt) + mag_lat = np.full(shape=geo_lt.shape, fill_value=mag_lat) + + # Determine if the time input is list-like + if hasattr(dtimes, 'year'): + dtime = dtimes + + # Set the AACGM coordinates of the geographic pole + methods = ["ALLOWTRACE"] + if self.vect_coord == "geocentric": + methods.append(self.vect_coord.upper()) + methods.append("A2G") + + if dtime is None: + mag_pole_glat = list() + mag_pole_slt = list() + method = '|'.join(methods) + for i, val in enumerate(dtimes): + # Get the geographic pole lat and lon + out = aacgmv2.convert_latlon( + hemisphere * 90.0, 0.0, self.height[i], val, method) + + # Save the SLT and latitude + mag_pole_slt.append(ocb_time.glon2slt(out[1], val)) + mag_pole_glat.append(out[0]) + else: + mag_pole_glat, mag_pole_lon = aacgmv2.convert_latlon( + hemisphere * 90.0, 0.0, self.height, dtime, + method_code='|'.join(methods)) + mag_pole_slt = ocb_time.glon2slt(mag_pole_lon, dtime) + + mag_pole_glat = np.asarray(mag_pole_glat) + mag_pole_slt = np.asarray(mag_pole_slt) + + # Get the angle at the data vector appended by the AACGM and + # geographic poles + pole_angle = vectors.calc_vec_pole_angle( + geo_lt, geo_lat, mag_pole_slt, mag_pole_glat) + + # Set the pole and vector quadrants + if np.any(~np.isnan(pole_angle)): + pole_quad = vectors.define_pole_quadrants(geo_lt, mag_pole_slt, + pole_angle) + vect_quad = vectors.define_vect_quadrants(self.vect_n, + self.vect_e) + + # Adjust the geographic vector to AACGM coordinates + mag_n, mag_e, mag_z = vectors.adjust_vector( + mag_lt, mag_lat, self.vect_n, self.vect_e, self.vect_z, + vect_quad, mag_pole_slt, mag_pole_glat, pole_angle, pole_quad) + + # Assign the new vector data and coordinate specification + self.vect_n = np.asarray(mag_n) + self.vect_e = np.asarray(mag_e) + self.vect_z = np.asarray(mag_z) + self.vect_coord = "magnetic" + + # Re-calculate the vector magnitude + self.vect_mag = np.nan return From 77cb1211761e3b04041ddb050ff1e7b13ebe3c78 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 11 Apr 2024 15:46:47 -0400 Subject: [PATCH 03/60] TST: updated OCB scaling unit tests Updated the OCB scaling unit tests by: - updating the error messages raised, - removing tests for updated quad lists from scaling vectors as this functionality is no longer present, and - updated the minimum amount of attribute names to reflect changes in the VectorData class. --- ocbpy/tests/test_ocb_scaling.py | 301 +++++++++++++++++--------------- 1 file changed, 156 insertions(+), 145 deletions(-) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index 195ce74b..42ea0af5 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -55,8 +55,8 @@ def test_no_scale_func(self): return def test_inconsistent_vector_warning(self): - """Test init failure with inconsistent AACGM components.""" - self.lwarn = u"inconsistent AACGM" + """Test init failure with inconsistent vector components.""" + self.lwarn = u"inconsistent vector components" # Initalize the VectorData class with inconsistent vector magnitudes self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, @@ -108,13 +108,13 @@ def tearDown(self): def test_init_nez(self): """Test the set up of the VectorData object without magnitude.""" - self.assertAlmostEqual(self.vdata.aacgm_mag, 100.036243432) - self.assertAlmostEqual(self.zdata.aacgm_mag, 0.0) + self.assertAlmostEqual(self.vdata.vect_mag, 100.036243432) + self.assertAlmostEqual(self.zdata.vect_mag, 0.0) return def test_init_mag(self): """Test the initialisation of the VectorData object with magnitude.""" - self.assertAlmostEqual(self.wdata.aacgm_mag, 100.036243432) + self.assertAlmostEqual(self.wdata.vect_mag, 100.036243432) return def test_repr_string(self): @@ -160,8 +160,9 @@ def test_vector_mult_ocb_ind(self): # Evaluate values are realistic and appropriately shaped for attr in self.ocb_attrs: - with self.subTest(attr=attr): - val = getattr(self.vdata, attr) + val = getattr(self.vdata, attr) + + with self.subTest(attr=attr, val=val): self.assertTupleEqual(self.vdata.ocb_ind.shape, val.shape) self.assertTrue(numpy.isfinite(val).all()) return @@ -170,10 +171,10 @@ def test_vector_mult_dat_ind(self): """Test the VectorData performance with multiple OCB indices.""" # Update the VectorData attribute to contain a list of all good indices test_shape = (3,) - self.vdata.aacgm_lat = numpy.full(shape=test_shape, - fill_value=self.vdata.aacgm_lat) - self.vdata.aacgm_mlt = numpy.full(shape=test_shape, - fill_value=self.vdata.aacgm_mlt) + self.vdata.lat = numpy.full(shape=test_shape, + fill_value=self.vdata.lat) + self.vdata.lt = numpy.full(shape=test_shape, + fill_value=self.vdata.lt) self.vdata.dat_ind = [i * 2 for i in range(test_shape[0])] # Set the VectorData OCB attributes @@ -220,7 +221,7 @@ def test_vector_clear_data(self): def test_vector_bad_lat(self): """Test the VectorData output with data from the wrong hemisphere.""" - self.vdata.aacgm_lat *= -1.0 + self.vdata.lat *= -1.0 self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) for attr in self.ocb_attrs: @@ -244,14 +245,14 @@ def test_calc_large_pole_angle(self): def test_calc_polar_angle_ocb_south_night(self): """Test `calc_polar_angle` with the OCB pole in a south/night quad.""" # Set a useful vector locaiton and intialise with current boundary - self.vdata.aacgm_mlt = 0.0 - self.vdata.aacgm_n = -10.0 - self.vdata.aacgm_e = -10.0 + self.vdata.lt = 0.0 + self.vdata.vect_n = -10.0 + self.vdata.vect_e = -10.0 self.vdata.set_ocb(self.ocb) # Change the location of the boundary center self.vdata.ocb_aacgm_mlt = 1.0 - self.vdata.ocb_aacgm_lat = self.vdata.aacgm_lat - 2.0 + self.vdata.ocb_aacgm_lat = self.vdata.lat - 2.0 # Update the quandrants self.vdata.calc_vec_pole_angle() @@ -264,12 +265,12 @@ def test_calc_polar_angle_ocb_south_night(self): def test_calc_polar_angle_ocb_south_day(self): """Test `calc_polar_angle` with the OCB pole in a south/day quad.""" # Set a useful vector locaiton and intialise with current boundary - self.vdata.aacgm_mlt = 0.0 + self.vdata.lt = 0.0 self.vdata.set_ocb(self.ocb) # Change the location of the boundary center self.vdata.ocb_aacgm_mlt = 1.0 - self.vdata.ocb_aacgm_lat = self.vdata.aacgm_lat - 2.0 + self.vdata.ocb_aacgm_lat = self.vdata.lat - 2.0 # Update the quandrants self.vdata.calc_vec_pole_angle() @@ -282,8 +283,8 @@ def test_calc_polar_angle_ocb_south_day(self): def test_big_pole_angle_mlt_west(self): """Test `calc_ocb_polar_angle` with a neg MLT, W vect, and big angle.""" # Get the original angle - self.vdata.aacgm_mlt = -22.0 - self.vdata.aacgm_e *= -1.0 + self.vdata.lt = -22.0 + self.vdata.vect_e *= -1.0 self.vdata.set_ocb(self.ocb) # Increase the pole angle enough to require an adjustment @@ -300,7 +301,7 @@ def test_calc_vec_pole_angle_acute(self): def test_calc_vec_pole_angle_zero(self): """Test the polar angle calculation with an angle of zero.""" self.vdata.set_ocb(self.ocb) - self.vdata.aacgm_mlt = self.vdata.ocb_aacgm_mlt + self.vdata.lt = self.vdata.ocb_aacgm_mlt self.vdata.calc_vec_pole_angle() self.assertEqual(self.vdata.pole_angle, 0.0) return @@ -309,8 +310,8 @@ def test_calc_vec_pole_angle_flat(self): """Test the polar angle calculation with an angle of 180 deg.""" self.vdata.set_ocb(self.ocb) self.vdata.ocb_aacgm_mlt = 6.0 - self.vdata.aacgm_mlt = 6.0 - self.vdata.aacgm_lat = 45.0 + 0.5 * self.vdata.ocb_aacgm_lat + self.vdata.lt = 6.0 + self.vdata.lat = 45.0 + 0.5 * self.vdata.ocb_aacgm_lat self.vdata.calc_vec_pole_angle() self.assertEqual(self.vdata.pole_angle, 180.0) return @@ -323,8 +324,8 @@ def test_calc_vec_pole_angle_right_isosceles(self): # the angle would be 45 degrees self.vdata.set_ocb(self.ocb) self.vdata.ocb_aacgm_mlt = 0.0 - self.vdata.aacgm_mlt = 6.0 - self.vdata.aacgm_lat = self.vdata.ocb_aacgm_lat + self.vdata.lt = 6.0 + self.vdata.lat = self.vdata.ocb_aacgm_lat self.vdata.calc_vec_pole_angle() self.assertAlmostEqual(self.vdata.pole_angle, 45.03325090532819) return @@ -332,8 +333,8 @@ def test_calc_vec_pole_angle_right_isosceles(self): def test_calc_vec_pole_angle_oblique(self): """Test the polar angle calculation with an isosceles triangle.""" self.vdata.set_ocb(self.ocb) - self.vdata.aacgm_mlt = self.vdata.ocb_aacgm_mlt - 1.0 - self.vdata.aacgm_lat = 45.0 + 0.5 * self.vdata.ocb_aacgm_lat + self.vdata.lt = self.vdata.ocb_aacgm_mlt - 1.0 + self.vdata.lat = 45.0 + 0.5 * self.vdata.ocb_aacgm_lat self.vdata.calc_vec_pole_angle() self.assertAlmostEqual(self.vdata.pole_angle, 150.9561733411) return @@ -344,8 +345,8 @@ def test_define_quadrants(self): self.vdata.ocb_aacgm_mlt = self.ocb.phi_cent[self.vdata.ocb_ind] / 15.0 self.vdata.ocb_aacgm_lat = 90.0 - self.ocb.r_cent[self.vdata.ocb_ind] (self.vdata.ocb_lat, self.vdata.ocb_mlt, - self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.aacgm_lat, - self.vdata.aacgm_mlt) + self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.lat, + self.vdata.lt) self.vdata.calc_vec_pole_angle() # Get the test quadrants @@ -356,10 +357,10 @@ def test_define_quadrants(self): def test_define_quadrants_neg_adj_mlt_west(self): """Test quadrant assignment with a negative AACGM MLT and W vect.""" - self.vdata.aacgm_mlt = -22.0 - self.vdata.aacgm_e *= -1.0 + self.vdata.lt = -22.0 + self.vdata.vect_e *= -1.0 self.vdata.set_ocb(self.ocb) - self.assertGreater(self.vdata.ocb_aacgm_mlt - self.vdata.aacgm_mlt, 24) + self.assertGreater(self.vdata.ocb_aacgm_mlt - self.vdata.lt, 24) self.assertEqual(self.vdata.ocb_quad, 1) self.assertEqual(self.vdata.vec_quad, 2) return @@ -367,7 +368,7 @@ def test_define_quadrants_neg_adj_mlt_west(self): def test_define_quadrants_neg_north(self): """Test the quadrant assignment with a vector pointing south.""" # Adjust the vector quadrant - self.vdata.aacgm_n *= -1.0 + self.vdata.vect_n *= -1.0 self.vdata.set_ocb(self.ocb) # Evaluate the output quadrants @@ -377,7 +378,7 @@ def test_define_quadrants_neg_north(self): def test_define_quadrants_noon_north(self): """Test quadrant assignment with a vector pointing north from noon.""" - self.vdata.aacgm_mlt = 12.0 + self.vdata.lt = 12.0 self.vdata.set_ocb(self.ocb) self.assertEqual(self.vdata.ocb_quad, 2) self.assertEqual(self.vdata.vec_quad, 1) @@ -386,9 +387,9 @@ def test_define_quadrants_noon_north(self): def test_define_quadrants_aligned_poles_southwest(self): """Test quad assignment w/vector pointing SW and both poles aligned.""" self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) - self.vdata.aacgm_mlt = self.vdata.ocb_aacgm_mlt + 12.0 - self.vdata.aacgm_n = -10.0 - self.vdata.aacgm_e = -10.0 + self.vdata.lt = self.vdata.ocb_aacgm_mlt + 12.0 + self.vdata.vect_n = -10.0 + self.vdata.vect_e = -10.0 self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) self.assertEqual(self.vdata.ocb_quad, 2) self.assertEqual(self.vdata.vec_quad, 3) @@ -396,10 +397,10 @@ def test_define_quadrants_aligned_poles_southwest(self): def test_define_quadrants_ocb_south_night(self): """Test quadrant assignment with the OCB pole in a south/night quad.""" - self.vdata.aacgm_mlt = 0.0 + self.vdata.lt = 0.0 self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) self.vdata.ocb_aacgm_mlt = 23.0 - self.vdata.ocb_aacgm_lat = self.vdata.aacgm_lat - 2.0 + self.vdata.ocb_aacgm_lat = self.vdata.lat - 2.0 self.vdata.calc_vec_pole_angle() self.vdata.define_quadrants() self.assertEqual(self.vdata.ocb_quad, 3) @@ -408,10 +409,10 @@ def test_define_quadrants_ocb_south_night(self): def test_define_quadrants_ocb_south_day(self): """Test quadrant assignment with the OCB pole in a south/day quad.""" - self.vdata.aacgm_mlt = 0.0 + self.vdata.lt = 0.0 self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) self.vdata.ocb_aacgm_mlt = 1.0 - self.vdata.ocb_aacgm_lat = self.vdata.aacgm_lat - 2.0 + self.vdata.ocb_aacgm_lat = self.vdata.lat - 2.0 self.vdata.calc_vec_pole_angle() self.vdata.define_quadrants() self.assertEqual(self.vdata.ocb_quad, 4) @@ -420,7 +421,7 @@ def test_define_quadrants_ocb_south_day(self): def test_undefinable_quadrants(self): """Test OCBScaling initialization for undefinable quadrants.""" - self.vdata.aacgm_lat = 0.0 + self.vdata.lat = 0.0 self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) self.assertEqual(self.vdata.ocb_quad, 0) self.assertEqual(self.vdata.vec_quad, 0) @@ -432,8 +433,9 @@ def test_lost_ocb_quadrant(self): self.assertEqual(self.vdata.ocb_quad, 1) self.assertEqual(self.vdata.vec_quad, 1) self.vdata.ocb_quad = 0 + + # If `ocb_quad` is not reset within this method, it raises a ValueError self.vdata.scale_vector() - self.assertEqual(self.vdata.ocb_quad, 1) return def test_lost_vec_quadrant(self): @@ -442,8 +444,9 @@ def test_lost_vec_quadrant(self): self.assertEqual(self.vdata.ocb_quad, 1) self.assertEqual(self.vdata.vec_quad, 1) self.vdata.vec_quad = 0 + + # If `vec_quad` is not reset within this method, it raises a ValueError self.vdata.scale_vector() - self.assertEqual(self.vdata.vec_quad, 1) return def test_calc_ocb_vec_sign(self): @@ -453,14 +456,14 @@ def test_calc_ocb_vec_sign(self): self.vdata.ocb_aacgm_mlt = self.ocb.phi_cent[self.vdata.ocb_ind] / 15.0 self.vdata.ocb_aacgm_lat = 90.0 - self.ocb.r_cent[self.vdata.ocb_ind] (self.vdata.ocb_lat, self.vdata.ocb_mlt, - self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.aacgm_lat, - self.vdata.aacgm_mlt) + self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.lat, + self.vdata.lt) self.vdata.calc_vec_pole_angle() self.vdata.define_quadrants() - vmag = numpy.sqrt(self.vdata.aacgm_n**2 + self.vdata.aacgm_e**2) + vmag = numpy.sqrt(self.vdata.vect_n**2 + self.vdata.vect_e**2) self.vdata.aacgm_naz = numpy.degrees(numpy.arccos( - self.vdata.aacgm_n / vmag)) + self.vdata.vect_n / vmag)) # Calculate the vector data signs vsigns = self.vdata.calc_ocb_vec_sign(north=True, east=True) @@ -476,14 +479,14 @@ def test_scale_vec(self): self.vdata.ocb_aacgm_mlt = self.ocb.phi_cent[self.vdata.ocb_ind] / 15.0 self.vdata.ocb_aacgm_lat = 90.0 - self.ocb.r_cent[self.vdata.ocb_ind] (self.vdata.ocb_lat, self.vdata.ocb_mlt, - self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.aacgm_lat, - self.vdata.aacgm_mlt) + self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.lat, + self.vdata.lt) self.vdata.calc_vec_pole_angle() self.vdata.define_quadrants() - vmag = numpy.sqrt(self.vdata.aacgm_n**2 + self.vdata.aacgm_e**2) + vmag = numpy.sqrt(self.vdata.vect_n**2 + self.vdata.vect_e**2) self.vdata.aacgm_naz = numpy.degrees(numpy.arccos( - self.vdata.aacgm_n / vmag)) + self.vdata.vect_n / vmag)) # Scale the data vector self.vdata.scale_vector() @@ -493,15 +496,15 @@ def test_scale_vec(self): self.assertAlmostEqual(self.vdata.ocb_e, 77.9686428950) # Test to see that the magnitudes and z-components are the same - self.assertAlmostEqual(self.vdata.aacgm_mag, self.vdata.ocb_mag) - self.assertAlmostEqual(self.vdata.ocb_z, self.vdata.aacgm_z) + self.assertAlmostEqual(self.vdata.vect_mag, self.vdata.ocb_mag) + self.assertAlmostEqual(self.vdata.ocb_z, self.vdata.vect_z) return def test_scale_vec_z_zero(self): """Test the calc of the OCB vector sign with no vertical aacgm_z.""" # Re-assing the necessary variable - self.vdata.aacgm_z = 0.0 + self.vdata.vect_z = 0.0 # Run the scale_vector routine self.vdata.set_ocb(self.ocb, scale_func=ocbpy.ocb_scaling.normal_evar) @@ -517,30 +520,35 @@ def test_scale_vec_pole_angle_zero(self): self.vdata.set_ocb(self.ocb) self.vdata.pole_angle = 0.0 - nscale = ocbpy.ocb_scaling.normal_evar(self.vdata.aacgm_n, + nscale = ocbpy.ocb_scaling.normal_evar(self.vdata.vect_n, self.vdata.unscaled_r, self.vdata.scaled_r) - escale = ocbpy.ocb_scaling.normal_evar(self.vdata.aacgm_e, + escale = ocbpy.ocb_scaling.normal_evar(self.vdata.vect_e, self.vdata.unscaled_r, self.vdata.scaled_r) # Cycle through all the possible options for a pole angle of zero/180 - for tset in [('scale_func', None, self.vdata.aacgm_n, - self.vdata.aacgm_e), + for tset in [('scale_func', None, self.vdata.vect_n, + self.vdata.vect_e), ('scale_func', ocbpy.ocb_scaling.normal_evar, nscale, escale), - ('ocb_aacgm_lat', self.vdata.aacgm_lat, -1.0 * nscale, - -1.0 * escale)]: - with self.subTest(tset=tset): - setattr(self.vdata, tset[0], tset[1]) + ('ocb_aacgm_lat', self.vdata.lat, -1.0 * self.vdata.vect_n, + -1.0 * self.vdata.vect_e)]: + # Update the vector data + self.hold_val = getattr(self.vdata, tset[0]) + setattr(self.vdata, tset[0], tset[1]) - # Run the scale_vector routine with the new attributes - self.vdata.scale_vector() + # Run the scale_vector routine with the new attributes + self.vdata.scale_vector() - # Assess the ocb north and east components + # Assess the ocb north and east components + with self.subTest(tset=tset): self.assertEqual(self.vdata.ocb_n, tset[2]) self.assertEqual(self.vdata.ocb_e, tset[3]) + # Reset the vector data + setattr(self.vdata, tset[0], self.hold_val) + return def test_set_ocb_zero(self): @@ -557,7 +565,7 @@ def test_set_ocb_none(self): # Set the OCB values without any E-field scaling, test to see that the # AACGM and OCB vector magnitudes are the same self.vdata.set_ocb(self.ocb) - self.assertAlmostEqual(self.vdata.aacgm_mag, self.vdata.ocb_mag) + self.assertAlmostEqual(self.vdata.vect_mag, self.vdata.ocb_mag) return def test_set_ocb_evar(self): @@ -652,14 +660,14 @@ def test_calc_ocb_vec_sign(self): self.vdata.ocb_aacgm_lat = 90.0 - self.ocb.ocb.r_cent[ self.vdata.ocb_ind] (self.vdata.ocb_lat, self.vdata.ocb_mlt, _, - self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.aacgm_lat, - self.vdata.aacgm_mlt) + self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.lat, + self.vdata.lt) self.vdata.calc_vec_pole_angle() self.vdata.define_quadrants() - vmag = numpy.sqrt(self.vdata.aacgm_n**2 + self.vdata.aacgm_e**2) + vmag = numpy.sqrt(self.vdata.vect_n**2 + self.vdata.vect_e**2) self.vdata.aacgm_naz = numpy.degrees(numpy.arccos( - self.vdata.aacgm_n / vmag)) + self.vdata.vect_n / vmag)) # Calculate the vector data signs vsigns = self.vdata.calc_ocb_vec_sign(north=True, east=True) @@ -677,14 +685,14 @@ def test_scale_vec(self): self.vdata.ocb_aacgm_lat = 90.0 - self.ocb.ocb.r_cent[ self.vdata.ocb_ind] (self.vdata.ocb_lat, self.vdata.ocb_mlt, _, - self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.aacgm_lat, - self.vdata.aacgm_mlt) + self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.lat, + self.vdata.lt) self.vdata.calc_vec_pole_angle() self.vdata.define_quadrants() - vmag = numpy.sqrt(self.vdata.aacgm_n**2 + self.vdata.aacgm_e**2) + vmag = numpy.sqrt(self.vdata.vect_n**2 + self.vdata.vect_e**2) self.vdata.aacgm_naz = numpy.degrees(numpy.arccos( - self.vdata.aacgm_n / vmag)) + self.vdata.vect_n / vmag)) # Scale the data vector self.vdata.scale_vector() @@ -696,9 +704,9 @@ def test_scale_vec(self): msg="eastern vector coordinates differe") # Test to see that the magnitudes and z-components are the same - self.assertAlmostEqual(self.vdata.aacgm_mag, self.vdata.ocb_mag, + self.assertAlmostEqual(self.vdata.vect_mag, self.vdata.ocb_mag, msg="vector magnitudes differ") - self.assertAlmostEqual(self.vdata.ocb_z, self.vdata.aacgm_z, + self.assertAlmostEqual(self.vdata.ocb_z, self.vdata.vect_z, msg="vector vertical commonents differ") return @@ -711,8 +719,8 @@ def test_define_quadrants(self): self.vdata.ocb_aacgm_lat = 90.0 - self.ocb.ocb.r_cent[ self.vdata.ocb_ind] (self.vdata.ocb_lat, self.vdata.ocb_mlt, _, - self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.aacgm_lat, - self.vdata.aacgm_mlt) + self.vdata.r_corr) = self.ocb.normal_coord(self.vdata.lat, + self.vdata.lt) self.vdata.calc_vec_pole_angle() # Get the test quadrants @@ -806,41 +814,32 @@ def test_init_vector_failure(self): def test_bad_calc_vec_pole_angle(self): """Test calc_vec_pole_angle failure with bad input.""" - self.input_attrs = ['aacgm_mlt', 'ocb_aacgm_mlt', 'aacgm_lat', - 'ocb_aacgm_lat'] - self.raise_out = ["AACGM MLT of Vector", "AACGM MLT of OCB pole", - "AACGM latitude of Vector", + # Define the different test combinations + self.input_attrs = ['lt', 'ocb_aacgm_mlt', 'lat', 'ocb_aacgm_lat'] + self.raise_out = ["Vector local time is undefined", + "AACGM MLT of OCB pole", + "Vector latitude is undefined", "AACGM latitude of OCB pole"] tsets = [(iattrs, bi, self.raise_out[i]) for i, iattrs in enumerate(self.input_attrs) for bi in self.bad_input] self.vdata.set_ocb(self.ocb, None) + # Cycle through the different test combinations for tset in tsets: - with self.subTest(tset=tset): - self.hold_val = getattr(self.vdata, tset[0]) - setattr(self.vdata, tset[0], tset[1]) + # Save the original value + self.hold_val = numpy.asarray(getattr(self.vdata, tset[0])) + + # Update to the bad value + setattr(self.vdata, tset[0], tset[1]) + with self.subTest(tset=tset): + # Test the appropriate error is raised with a bad value with self.assertRaisesRegex(ValueError, tset[2]): self.vdata.calc_vec_pole_angle() - setattr(self.vdata, tset[0], self.hold_val) - return - - def test_no_ocb_lat(self): - """Test failure when OCB latitude is not available.""" - self.vdata.ocb_lat = numpy.nan - - with self.assertRaisesRegex(ValueError, 'OCB coordinates required'): - self.vdata.scale_vector() - return - - def test_no_ocb_mlt(self): - """Test failure when OCB MLT is not available.""" - self.vdata.ocb_mlt = numpy.nan - - with self.assertRaisesRegex(ValueError, 'OCB coordinates required'): - self.vdata.scale_vector() + # Reset to the good value + setattr(self.vdata, tset[0], self.hold_val) return def test_no_ocb_pole_location(self): @@ -867,16 +866,18 @@ def test_bad_ocb_quad(self): self.vdata.set_ocb(self.ocb, None) self.vdata.ocb_quad = -1 - with self.assertRaisesRegex(ValueError, "OCB quadrant undefined"): + with self.assertRaisesRegex( + ValueError, "destination coordinate pole quadrant is "): self.vdata.calc_ocb_polar_angle() return def test_bad_vec_quad(self): """Test failure when vector quadrant is wrong.""" - self.vdata.set_ocb(self.ocb, None) + self.vdata.set_ocb(self.ocb, scale_func=None) self.vdata.vec_quad = -1 - with self.assertRaisesRegex(ValueError, "Vector quadrant undefined"): + with self.assertRaisesRegex(ValueError, + "data vector quadrant is undefined"): self.vdata.calc_ocb_polar_angle() return @@ -886,7 +887,7 @@ def test_bad_quad_polar_angle(self): self.vdata.aacgm_naz = numpy.nan with self.assertRaisesRegex(ValueError, - "AACGM polar angle undefined"): + "AACGM North polar angle undefined"): self.vdata.calc_ocb_polar_angle() return @@ -913,7 +914,8 @@ def test_bad_calc_sign_ocb_quad(self): self.vdata.set_ocb(self.ocb, None) self.vdata.ocb_quad = -1 - with self.assertRaisesRegex(ValueError, "OCB quadrant undefined"): + with self.assertRaisesRegex( + ValueError, "destination coordinate pole quadrant is"): self.vdata.calc_ocb_vec_sign(north=True) return @@ -922,7 +924,8 @@ def test_bad_calc_sign_vec_quad(self): self.vdata.set_ocb(self.ocb, None) self.vdata.vec_quad = -1 - with self.assertRaisesRegex(ValueError, "Vector quadrant undefined"): + with self.assertRaisesRegex(ValueError, + "data vector quadrant is undefined"): self.vdata.calc_ocb_vec_sign(north=True) return @@ -945,7 +948,7 @@ def test_bad_calc_sign_pole_angle(self): self.vdata.calc_ocb_vec_sign(north=True) return - def test_bad_define_quandrants_pole_mlt(self): + def test_bad_define_quadrants_pole_mlt(self): """Test define_quadrants failure with bad pole MLT.""" self.vdata.set_ocb(self.ocb, None) self.vdata.ocb_aacgm_mlt = numpy.nan @@ -954,17 +957,17 @@ def test_bad_define_quandrants_pole_mlt(self): self.vdata.define_quadrants() return - def test_bad_define_quandrants_vec_mlt(self): + def test_bad_define_quadrants_vec_mlt(self): """Test define_quadrants failure with bad vector MLT.""" self.vdata.set_ocb(self.ocb, None) - self.vdata.aacgm_mlt = numpy.nan + self.vdata.lt = numpy.nan with self.assertRaisesRegex(ValueError, - "Vector AACGM location required"): + "Vector location required"): self.vdata.define_quadrants() return - def test_bad_define_quandrants_pole_angle(self): + def test_bad_define_quadrants_pole_angle(self): """Test define_quadrants failure with bad pole angle.""" self.vdata.set_ocb(self.ocb, None) self.vdata.pole_angle = numpy.nan @@ -1095,9 +1098,9 @@ def setUp(self): 4, 1]} self.vargs = [numpy.arange(0, 17, 1), 27, lats, mlts] - self.vkwargs = {'aacgm_n': numpy.array(north), - 'aacgm_e': numpy.array(east), - 'aacgm_z': numpy.array(vert), 'dat_name': 'Test', + self.vkwargs = {'vect_n': numpy.array(north), + 'vect_e': numpy.array(east), + 'vect_z': numpy.array(vert), 'dat_name': 'Test', 'dat_units': 'm/s'} self.vdata = None self.out = None @@ -1124,7 +1127,7 @@ def test_array_vector_str_not_calc(self): self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.out = str(self.vdata) self.assertRegex(self.out, "Index") - self.assertRegex(self.out, "nan, nan, {:d}".format(self.vargs[1])) + self.assertRegex(self.out, "nan, nan, N/A, {:d}".format(self.vargs[1])) return def test_array_vector_str_calc(self): @@ -1152,9 +1155,9 @@ def test_array_vector_str_calc_ocb_array(self): self.set_vector_ocb_ind() self.vargs[2] = self.vargs[2][0] self.vargs[3] = self.vargs[3][0] - self.vkwargs['aacgm_n'] = self.vkwargs['aacgm_n'][0] - self.vkwargs['aacgm_e'] = self.vkwargs['aacgm_e'][0] - self.vkwargs['aacgm_z'] = self.vkwargs['aacgm_z'][0] + self.vkwargs['vect_n'] = self.vkwargs['vect_n'][0] + self.vkwargs['vect_e'] = self.vkwargs['vect_e'][0] + self.vkwargs['vect_z'] = self.vkwargs['vect_z'][0] self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.vdata.set_ocb(self.ocb) self.out = str(self.vdata) @@ -1165,9 +1168,9 @@ def test_array_vector_str_calc_ocb_array(self): def test_init_nez_vec_array(self): """Test VectorData initialisation with vector array components.""" self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) - self.assertEqual(len(self.vdata.aacgm_mag), len(self.vargs[0])) + self.assertEqual(len(self.vdata.vect_mag), len(self.vargs[0])) self.assertEqual(len(self.vdata.ocb_mag), len(self.vargs[0])) - for i, self.out in enumerate(self.vdata.aacgm_mag): + for i, self.out in enumerate(self.vdata.vect_mag): self.assertAlmostEqual(self.out, self.aacgm_mag[i]) return @@ -1175,9 +1178,9 @@ def test_init_nez_ocb_vec_array(self): """Test VectorData set up with ocb and vector array components.""" self.set_vector_ocb_ind() self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) - self.assertEqual(len(self.vdata.aacgm_mag), len(self.vargs[0])) + self.assertEqual(len(self.vdata.vect_mag), len(self.vargs[0])) self.assertEqual(len(self.vdata.ocb_mag), len(self.vargs[0])) - for i, self.out in enumerate(self.vdata.aacgm_mag): + for i, self.out in enumerate(self.vdata.vect_mag): self.assertAlmostEqual(self.out, self.aacgm_mag[i]) return @@ -1186,13 +1189,13 @@ def test_init_nez_ocb_array(self): self.set_vector_ocb_ind() self.vargs[2] = self.vargs[2][0] self.vargs[3] = self.vargs[3][0] - self.vkwargs['aacgm_n'] = self.vkwargs['aacgm_n'][0] - self.vkwargs['aacgm_e'] = self.vkwargs['aacgm_e'][0] - self.vkwargs['aacgm_z'] = self.vkwargs['aacgm_z'][0] + self.vkwargs['vect_n'] = self.vkwargs['vect_n'][0] + self.vkwargs['vect_e'] = self.vkwargs['vect_e'][0] + self.vkwargs['vect_z'] = self.vkwargs['vect_z'][0] self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.assertEqual(len(self.vdata.ocb_mag), len(self.vargs[1])) - self.assertTrue(abs(self.vdata.aacgm_mag - self.aacgm_mag[0]).all() + self.assertTrue(abs(self.vdata.vect_mag - self.aacgm_mag[0]).all() < 1.0e-7, msg="unexpected AACGM vector magnitude") return @@ -1200,8 +1203,8 @@ def test_init_mag(self): """Test the set up of the VectorData array input with magnitude.""" self.vkwargs['aacgm_mag'] = self.aacgm_mag self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) - self.assertEqual(len(self.vdata.aacgm_mag), len(self.vargs[0])) - for i, self.out in enumerate(self.vdata.aacgm_mag): + self.assertEqual(len(self.vdata.vect_mag), len(self.vargs[0])) + for i, self.out in enumerate(self.vdata.vect_mag): self.assertAlmostEqual(self.out, self.aacgm_mag[i]) return @@ -1221,7 +1224,7 @@ def test_vector_all_bad_lat(self): # Ensure that input is not overwritten for vkey in self.vkwargs.keys(): self.out = getattr(self.vdata, vkey) - if vkey.find('aacgm_') == 0: + if vkey.find('vect_') == 0: for i, val in enumerate(self.vkwargs[vkey]): self.assertEqual(self.out[i], val) else: @@ -1230,10 +1233,14 @@ def test_vector_all_bad_lat(self): def test_vector_some_bad_lat(self): """Test the VectorData output with mixed hemisphere input.""" + # Change the hemisphere of one of the latitudes self.vargs[2][0] *= -1.0 + + # Set and process vector data self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.vdata.set_ocb(self.ocb) + # Evaluate OCB vector locations self.assertTrue(len(self.vdata.ocb_lat), len(self.vargs[2])) # Ensure the wrong hemisphere is NaN @@ -1246,14 +1253,14 @@ def test_vector_some_bad_lat(self): # Ensure that input is not overwritten for vkey in self.vkwargs.keys(): self.out = getattr(self.vdata, vkey) - if vkey.find('aacgm_') == 0: + if vkey.find('vect_') == 0: for i, val in enumerate(self.vkwargs[vkey]): self.assertEqual(self.out[i], val) else: self.assertRegex(self.out, self.vkwargs[vkey]) # Ensure the right hemisphere is good - self.assertAlmostEqual(self.vdata.aacgm_mag[1], self.aacgm_mag[1]) + self.assertAlmostEqual(self.vdata.vect_mag[1], self.aacgm_mag[1]) self.assertAlmostEqual(self.vdata.ocb_mag[1], self.aacgm_mag[1]) return @@ -1307,7 +1314,7 @@ def test_one_undefinable_ocb_quadrant(self): def test_one_undefinable_vec_quadrant(self): """Test VectorData array set up for a undefinable vec quadrant.""" - self.vkwargs['aacgm_n'][1] = numpy.nan + self.vkwargs['vect_n'][1] = numpy.nan self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.vdata.set_ocb(self.ocb) @@ -1331,32 +1338,36 @@ def test_scale_vec_pole_angle_zero(self): """Test the calculation of the OCB vector sign with no pole angle.""" self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.vdata.set_ocb(self.ocb) - + # If the OCB pole is closer to the AACGM pole than the vector, set # the pole angle to zero deg. Otherwise, set it to 180.0 deg self.vdata.pole_angle = numpy.zeros(shape=self.vargs[2].shape) self.vdata.pole_angle[self.vdata.ocb_quad > 2] = 180.0 - nscale = ocbpy.ocb_scaling.normal_evar(self.vdata.aacgm_n, + nscale = ocbpy.ocb_scaling.normal_evar(self.vdata.vect_n, self.vdata.unscaled_r, self.vdata.scaled_r) - escale = ocbpy.ocb_scaling.normal_evar(self.vdata.aacgm_e, + escale = ocbpy.ocb_scaling.normal_evar(self.vdata.vect_e, self.vdata.unscaled_r, self.vdata.scaled_r) # Cycle through all the possible options for a pole angle of zero/180 - for tset in [('scale_func', None, self.vkwargs['aacgm_n'], - self.vkwargs['aacgm_e']), + for tset in [('scale_func', None, self.vkwargs['vect_n'], + self.vkwargs['vect_e']), ('scale_func', ocbpy.ocb_scaling.normal_evar, nscale, escale)]: - with self.subTest(tset=tset): - setattr(self.vdata, tset[0], tset[1]) + # Update the test attribute + self.hold_val = getattr(self.vdata, tset[0]) + setattr(self.vdata, tset[0], tset[1]) - # Run the scale_vector routine with the new attributes - self.vdata.scale_vector() + # Run the scale_vector routine with the new attributes + self.vdata.scale_vector() - # Assess the ocb north and east components + # Assess the ocb north and east components + with self.subTest(tset=tset): self.assertTrue(numpy.all(self.vdata.ocb_n == tset[2])) self.assertTrue(numpy.all(self.vdata.ocb_e == tset[3])) + # Reset the test attribute + setattr(self.vdata, tset[0], self.hold_val) return From 4c2f91d082aa515a1fdd328de6f8fd416f155786 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 11 Apr 2024 15:48:34 -0400 Subject: [PATCH 04/60] DOC: updated changelog Added a description of the vector changes to the changelog. --- Changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index b1138da2..37560e99 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -15,6 +15,9 @@ Summary of all changes made since the first stable release * ENH: Added AMPERE EABs, using the Heppner-Maynard boundary as a valid EAB * ENH: Changed default directory ID to use `pathlib` * ENH: Allow data padding in `pysat_instrument` functions +* ENH: Created separate vector transformation functions to support multiple + coordinate systems +* ENH: Updated VectorData to allow geographic vector inputs * BUG: Fixed a typo in the documentation's pysat example * BUG: Added an error catch for badly formatted SuperMAG file reading * TST: Added a new CI test for the Test PyPi Release Candidate From f76a9645785b0968bbbf6779c7ecdf559b8ed080 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Fri, 12 Apr 2024 09:36:32 -0400 Subject: [PATCH 05/60] BUG: added range fixing Added range fixing to all time conversion functions. --- ocbpy/ocb_time.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ocbpy/ocb_time.py b/ocbpy/ocb_time.py index 7d79b320..19b2c9ee 100644 --- a/ocbpy/ocb_time.py +++ b/ocbpy/ocb_time.py @@ -215,16 +215,22 @@ def deg2hr(lon): lon = np.asarray(lon) lt = lon / 15.0 # 12 hr/180 deg = 1/15 hr/deg + # Ensure the local time range is realistic + lt = fix_range(lt, 0.0, 24.0) + return lt -def hr2deg(lt): +def hr2deg(lt, max_deg=360.0): """Convert from degrees to hours. Parameters ---------- lt : float or array-like Local time-like value in hours + max_deg : float + Maximum number of degrees in desired range, e.g. 360 for 0-360 or + 180 for +/-180 (default=360.0) Returns ------- @@ -236,16 +242,22 @@ def hr2deg(lt): lt = np.asarray(lt) lon = lt * 15.0 # 180 deg/12 hr = 15 deg/hr + # Ensure the local time range is realistic + lon = fix_range(lon, max_deg - 360.0, max_deg) + return lon -def hr2rad(lt): +def hr2rad(lt, max_range=2.0 * np.pi): """Convert from hours to radians. Parameters ---------- lt : float or array-like Local time-like value in hours + max_range : float + Maximum radians in desired range, e.g. 2pi for 0-2pi or pi for +/-pi + (default=2.0 * np.pi) Returns ------- @@ -257,6 +269,9 @@ def hr2rad(lt): lt = np.asarray(lt) lon = lt * np.pi / 12.0 + # Ensure the longitude range is realistic + lon = fix_range(lon, max_range - (2.0 * np.pi), max_range) + return lon @@ -278,6 +293,9 @@ def rad2hr(lon): lon = np.asarray(lon) lt = lon * 12.0 / np.pi + # Ensure the local time range is realistic + lt = fix_range(lt, 0.0, 24.0) + return lt From 409f809b13cb453c18ae23747877ac3c0178a5f0 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Fri, 12 Apr 2024 09:36:51 -0400 Subject: [PATCH 06/60] TST: updated time unit tests Updated the time unit tests for range fixing. --- ocbpy/tests/test_ocb_time.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ocbpy/tests/test_ocb_time.py b/ocbpy/tests/test_ocb_time.py index e831cd69..6d48921f 100644 --- a/ocbpy/tests/test_ocb_time.py +++ b/ocbpy/tests/test_ocb_time.py @@ -190,61 +190,89 @@ def tearDown(self): def test_deg2hr_array(self): """Test degree to hour conversion for an array.""" + # Adjust the expected output + self.lt[-1] = self.lt[0] + + # Run the test function self.out = ocb_time.deg2hr(self.lon) + # Evaluate the output for i, val in enumerate(self.lt): self.assertAlmostEqual(self.out[i], val) return def test_deg2hr_value(self): """Test degree to hour conversion for a single value.""" + # Run the test function self.out = ocb_time.deg2hr(self.lon[0]) + # Evaluate the output self.assertAlmostEqual(self.out, self.lt[0]) return def test_hr2deg_array(self): """Test hour to degree conversion for an array.""" + # Adjust the expected output + self.lon[-1] = self.lon[0] + + # Run the test function self.out = ocb_time.hr2deg(self.lt) + # Evaluate the output for i, val in enumerate(self.lon): self.assertAlmostEqual(self.out[i], val) return def test_hr2deg_value(self): """Test hour to degree conversion for a single value.""" + # Run the test function self.out = ocb_time.deg2hr(self.lt[0]) + # Evaluate the output self.assertAlmostEqual(self.out, self.lon[0]) return def test_hr2rad_array(self): """Test hour to radian conversion for an array.""" + # Adjust the expected output + self.lon[-1] = self.lon[0] + + # Run the test function self.out = ocb_time.hr2rad(self.lt) + # Evaluate the output for i, val in enumerate(np.radians(self.lon)): self.assertAlmostEqual(self.out[i], val) return def test_hr2rad_value(self): """Test hour to radian conversion for a single value.""" + # Run the test function self.out = ocb_time.hr2rad(self.lt[0]) + # Evaluate the output self.assertAlmostEqual(self.out, np.radians(self.lon[0])) return def test_rad2hr_array(self): """Test radian to hour conversion for an array.""" + # Adjust the expected output + self.lt[-1] = self.lt[0] + + # Run the test function self.out = list(ocb_time.rad2hr(np.radians(self.lon))) + # Evaluate the output for i, val in enumerate(self.out): self.assertAlmostEqual(val, self.lt[i]) return def test_rad2hr_value(self): """Test radian to hour conversion for a single value.""" + # Run the test function self.out = ocb_time.rad2hr(np.radians(self.lon[0])) + # Evaluate the output self.assertAlmostEqual(self.out, self.lt[0]) return From 43b5e5264f38d62de46873c042e1a2bedb91cf3d Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Fri, 12 Apr 2024 09:37:59 -0400 Subject: [PATCH 07/60] DOC: updated docstring Updated the description of the `get_pole_loc` function. --- ocbpy/vectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index a9153828..1fe24de9 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -10,7 +10,7 @@ def get_pole_loc(phi_cent, r_cent): - """Get the location of a pole of one coordinate system in another one. + """Convert a second coordinate system's pole location to lt/lat. Parameters ---------- From fd338097805717748c4697ce96fa7e8691860ede Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Fri, 12 Apr 2024 09:38:27 -0400 Subject: [PATCH 08/60] TST: added unit tests for vectors Created a test suite for the vector functions. --- ocbpy/tests/test_vectors.py | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 ocbpy/tests/test_vectors.py diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py new file mode 100644 index 00000000..01df6e5b --- /dev/null +++ b/ocbpy/tests/test_vectors.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2017, AGB & GC +# Full license can be found in License.md +# ----------------------------------------------------------------------------- +"""Tests the functions for vector transformation.""" + +import numpy as np +import unittest + +from ocbpy import vectors + + +class TestOCBVectors(unittest.TestCase): + """Unit tests for the vectors functions.""" + + def setUp(self): + """Initialize the test class.""" + self.out = None + self.comp = None + return + + def tearDown(self): + """Clean up the test class.""" + del self.out, self.comp + return + + def test_get_pole_loc_float(self): + """Test convertion of one pole loc from relative to lt/lat coords.""" + # Cycle through a range of inputs and outputs + for phi, rad, self.comp in [(0, 5, (0.0, 85.0)), (90, 4, (6.0, 86.0)), + (180, 0, (12.0, 90.0)), + (270, 1, (18.0, 89.0)), + (360, 1, (0.0, 89.0))]: + with self.subTest(phi=phi, rad=rad): + # Convert the input + self.out = vectors.get_pole_loc(phi, rad) + + # Evaluate the output + self.assertTupleEqual(self.out, self.comp) + return + + def test_get_pole_loc_array(self): + """Test convertion of one pole loc from relative to lt/lat coords.""" + # Set the inputs and expected outputs + phi = [0.0, 90.0, 180.0, 270.0, 360.0, 450.0] + rad = [5.0, 4.0, 3.0, 2.0, 1.0, 6.0, 0.0] + self.comp = (np.array([0.0, 6.0, 12.0, 18.0, 0.0, 6.0]), + np.array([85.0, 86.0, 87.0, 88.0, 89.0, 84.0, 90.0])) + + # Convert the input + self.out = vectors.get_pole_loc(phi, rad) + + # Evaluate the outputs + for i, comp_array in enumerate(self.comp): + self.assertTrue(np.all(self.out[i] == comp_array), + msg="unexpected array output: {:} != {:}".format( + self.out[i], comp_array)) + return From c545ee0489e1a3b8d7a2dc57e8b617ffd306f1f6 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 17 Apr 2024 15:10:14 -0400 Subject: [PATCH 09/60] BUG: fixed array flat-angle assignment Ensure latitudes are array-like and that flat/zero masking is correct for array-like input when calculating the pole-data-pole angle. --- ocbpy/vectors.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index 1fe24de9..0b26ace9 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -83,6 +83,10 @@ def calc_vec_pole_angle(data_lt, data_lat, pole_lt, pole_lat): # Initalize the output pole_angle = np.full(shape=del_long.shape, fill_value=np.nan) + # Ensure the latitudes are array-like + data_lat = np.asarray(data_lat) + pole_lat = np.asarray(pole_lat) + # Assign the extreme values if len(del_long.shape) == 0: if del_long in [-np.pi, 0.0, np.pi]: @@ -92,10 +96,10 @@ def calc_vec_pole_angle(data_lt, data_lat, pole_lt, pole_lat): pole_angle = 0.0 return pole_angle else: - zero_mask = (((del_long == 0) | (abs(del_long) == np.pi)) + flat_mask = (((del_long == 0) | (abs(del_long) == np.pi)) & np.greater(abs(data_lat), abs(pole_lat), where=~np.isnan(del_long))) - flat_mask = (((del_long == 0) | (abs(del_long) == np.pi)) + zero_mask = (((del_long == 0) | (abs(del_long) == np.pi)) & np.less_equal(abs(data_lat), abs(pole_lat), where=~np.isnan(del_long))) From 9b4011bc3b06899ab98add513367dcb799aee736 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 17 Apr 2024 15:10:50 -0400 Subject: [PATCH 10/60] TST: added `calc_vec_pole_angle` tests Added unit tests for the `calc_vec_pole_angle` function. --- ocbpy/tests/test_vectors.py | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py index 01df6e5b..dbe9f37b 100644 --- a/ocbpy/tests/test_vectors.py +++ b/ocbpy/tests/test_vectors.py @@ -57,3 +57,75 @@ def test_get_pole_loc_array(self): msg="unexpected array output: {:} != {:}".format( self.out[i], comp_array)) return + + def test_calc_vec_pole_angle_float(self): + """Test angle calc between base pole, data, and new pole for floats.""" + # Set locations designed to test no angle, flat lines, acute angles, + # isocelese triangles, and oblique angles + lt = [0.0, 24.0, 6.0, 21.22, 22.0, 6.0, 6.0, 4.832] + lat = [50.0, 50.0, 50.0, 87.2, 75.0, 88.62, 87.24, 88.62] + pole_lt = [0.0, 12.0, 18.0, 1.260677777, 5.832, 6.0, 0.0, 5.832] + pole_lat = [88.0, 88.0, 88.0, 83.99, 87.24, 87.24, 87.24, 87.24] + self.comp = [0.0, 0.0, 0.0, 91.72024697182087, 8.67527923, 180.0, + 45.03325090532819, 150.9561733411] + + for hemi in [-1, 1]: + for i, cval in enumerate(self.comp): + # Set the input arguments + args = [lt[i], hemi * lat[i], pole_lt[i], hemi * pole_lat[i]] + + with self.subTest(args=args): + # Calculate the pole angle + self.out = vectors.calc_vec_pole_angle(*args) + + # Test the output + self.assertAlmostEqual(self.out, cval) + return + + def test_calc_vec_pole_angle_array(self): + """Test angle calc between base pole, data, and new pole for arrays.""" + # Set locations designed to test no angle, flat lines, acute angles, + # isocelese triangles, and oblique angles + lt = np.array([0.0, 24.0, 6.0, 21.22, 22.0, 6.0, 6.0, 4.832]) + lat = np.array([50.0, 50.0, 50.0, 87.2, 75.0, 88.62, 87.24, 88.62]) + pole_lt = np.array([0.0, 12.0, 18.0, 1.260677777, 5.832, 6.0, 0.0, + 5.832]) + pole_lat = np.array([88.0, 88.0, 88.0, 83.99, 87.24, 87.24, 87.24, + 87.24]) + self.comp = np.array([0.0, 0.0, 0.0, 91.72024697182087, 8.67527923, + 180.0, 45.03325090532819, 150.9561733411]) + + for hemi in [-1, 1]: + with self.subTest(hemi=hemi): + # Calculate the pole angle + self.out = vectors.calc_vec_pole_angle( + lt, hemi * lat, pole_lt, hemi * pole_lat) + + # Test the output + self.assertTrue(np.all(abs(self.out - self.comp) < 1e-5), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_calc_vec_pole_angle_mixed(self): + """Test angle calc for base pole, data, new pole with float and list.""" + # Set locations designed to test no angle, flat lines, acute angles, + # isocelese triangles, and oblique angles + + for args, self.comp in [ + ([0.0, -50.0, [0.0, 12.0], -88.0], np.zeros(shape=2)), + ([[6.0, 21.22], [50.0, 87.2], [18.0, 1.26067777], 83.99], + np.array([0.0, 91.72024697182087])), + ([6.0, [88.62, 87.24], [6.0, 0.0], 87.24], + np.array([180.0, 45.03325090532819]))]: + with self.subTest(args=args): + # Calculate the pole angle + self.out = vectors.calc_vec_pole_angle(*args) + + # Test the output + self.assertTrue(self.out.shape == self.comp.shape) + self.assertTrue(np.all(abs(self.out - self.comp) < 1e-5), + msg="{:} != {:}".format(self.out, self.comp)) + return + + + From e77ec3a8057148304256a34b888b953c55b39ce6 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 17 Apr 2024 15:11:57 -0400 Subject: [PATCH 11/60] BUG: updated unit tests Fixed the quadrant assignment in the `test_calc_vec_pole_angle_flat` integration test. Also updated docstrings to be more correct. --- ocbpy/tests/test_ocb_scaling.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index 42ea0af5..b941c2d1 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -235,8 +235,6 @@ def test_calc_large_pole_angle(self): """Test the OCB polar angle calculation with angles > 90 deg.""" self.zdata.ocb_aacgm_mlt = 1.260677777 self.zdata.ocb_aacgm_lat = 83.99 - self.zdata.ocb_lat = 84.838777192 - self.zdata.ocb_mlt = 15.1110383783 self.zdata.calc_vec_pole_angle() self.assertAlmostEqual(self.zdata.pole_angle, 91.72024697182087) @@ -331,7 +329,7 @@ def test_calc_vec_pole_angle_right_isosceles(self): return def test_calc_vec_pole_angle_oblique(self): - """Test the polar angle calculation with an isosceles triangle.""" + """Test the polar angle calculation with an oblique triangle.""" self.vdata.set_ocb(self.ocb) self.vdata.lt = self.vdata.ocb_aacgm_mlt - 1.0 self.vdata.lat = 45.0 + 0.5 * self.vdata.ocb_aacgm_lat @@ -1273,15 +1271,15 @@ def test_calc_vec_pole_angle_flat(self): self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.vdata.set_ocb(self.ocb) - self.assertTrue(numpy.all([quad in [2, 4] + self.assertTrue(numpy.all([quad in [1, 3] for quad in self.vdata.ocb_quad])) self.assertEqual(list(self.vdata.vec_quad), self.ref_quads['vec']) self.assertTrue(numpy.all([self.vdata.pole_angle[i] == 0.0 for i, quad in enumerate(self.vdata.ocb_quad) - if quad == 2])) + if quad == 1])) self.assertTrue(numpy.all([self.vdata.pole_angle[i] == 180.0 for i, quad in enumerate(self.vdata.ocb_quad) - if quad == 4])) + if quad == 3])) return def test_array_vec_quad(self): From 9cd4b6b7d1364e78f7674043cd1ae259946326a8 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 17 Apr 2024 15:12:12 -0400 Subject: [PATCH 12/60] DOC: update changelog Added the new bug that is fixed to the changelog. --- Changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.rst b/Changelog.rst index 37560e99..cf7ac09e 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -20,6 +20,7 @@ Summary of all changes made since the first stable release * ENH: Updated VectorData to allow geographic vector inputs * BUG: Fixed a typo in the documentation's pysat example * BUG: Added an error catch for badly formatted SuperMAG file reading +* BUG: Fixed the flat/zero masking for vector pole angles with array input * TST: Added a new CI test for the Test PyPi Release Candidate * TST: Reduced duplication by creating a common test class and test variable * TST: Added a ReadTheDocs yaml From 9be8774408aa76661a975e5f716333087d0073ad Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 23 Apr 2024 11:48:47 -0400 Subject: [PATCH 13/60] BUG: improved pole quad robustness Improved the robustness of the destination pole quadrant identification by: - setting the size after finding the time difference instead of assuming it will always be equal to the pole angle, - testing the cycle condition for floats as well as arrays, and - testing the cycle condition for large angles as well as negative angles. --- ocbpy/vectors.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index 0b26ace9..4bdae668 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -169,17 +169,18 @@ def define_pole_quadrants(data_lt, pole_lt, pole_angle): downwards. Quadrants: 1 [N, E]; 2 [N, W]; 3 [S, W]; 4 [S, E]; 0 [undefined] """ - # Initalize the output - pole_quad = np.zeros(shape=np.asarray(pole_angle).shape) - # Determine where the destination pole is relative to the data vector del_lt = np.asarray(pole_lt) - np.asarray(data_lt) + # Initalize the output + pole_quad = np.zeros(shape=np.asarray(del_lt).shape) + + # Determine which differences need to be neg_mask = np.less(del_lt, 0.0, where=~np.isnan(del_lt)) & ~np.isnan(del_lt) while np.any(neg_mask): if len(del_lt.shape) == 0: del_lt += 24.0 - neg_mask = [False] + neg_mask = np.less(del_lt, 0.0) # Has one finite value else: del_lt[neg_mask] += 24.0 neg_mask = np.less(del_lt, 0.0, @@ -187,11 +188,15 @@ def define_pole_quadrants(data_lt, pole_lt, pole_angle): large_mask = np.greater_equal(abs(del_lt), 24.0, where=~np.isnan(del_lt)) & ~np.isnan(del_lt) - if np.any(large_mask): + while np.any(large_mask): if len(del_lt.shape) == 0: del_lt -= 24.0 * np.sign(del_lt) + large_mask = np.greater_equal(abs(del_lt), 24.0) # One finite value else: del_lt[large_mask] -= 24.0 * np.sign(del_lt[large_mask]) + large_mask = np.greater_equal(abs(del_lt), 24.0, + where=~np.isnan(del_lt)) & ~np.isnan( + del_lt) # Find the quadrant in which the OCB pole lies nan_mask = ~np.isnan(pole_angle) & ~np.isnan(del_lt) From 539e79bbd45ebdfec023145a9db0a9e7f7ad623e Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 23 Apr 2024 11:49:53 -0400 Subject: [PATCH 14/60] TST: added unit tests for `define_pole_quadrants` Added unit tests for `define_pole_quadrants` and simplified unit tests by making some of the variables class variables. --- ocbpy/tests/test_vectors.py | 149 ++++++++++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 23 deletions(-) diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py index dbe9f37b..96f439bf 100644 --- a/ocbpy/tests/test_vectors.py +++ b/ocbpy/tests/test_vectors.py @@ -16,13 +16,19 @@ class TestOCBVectors(unittest.TestCase): def setUp(self): """Initialize the test class.""" + self.lt = [0.0, 24.0, 6.0, 21.22, 22.0, 6.0, 6.0, 0.0] + self.lat = [50.0, 50.0, 50.0, 87.2, 75.0, 88.62, 87.24, 89.0] + self.pole_lt = [0.0, 12.0, 18.0, 1.260677777, 5.832, 6.0, 0.0, 22.0] + self.pole_lat = [88.0, 88.0, 88.0, 83.99, 87.24, 87.24, 87.24, 85.0] + self.pole_ang = [0.0, 0.0, 0.0, 91.72024697182087, 8.67527923, 180.0, + 45.03325090532819, 143.11957692472973] self.out = None self.comp = None return def tearDown(self): """Clean up the test class.""" - del self.out, self.comp + del self.lt, self.lat, self.out, self.comp return def test_get_pole_loc_float(self): @@ -62,44 +68,36 @@ def test_calc_vec_pole_angle_float(self): """Test angle calc between base pole, data, and new pole for floats.""" # Set locations designed to test no angle, flat lines, acute angles, # isocelese triangles, and oblique angles - lt = [0.0, 24.0, 6.0, 21.22, 22.0, 6.0, 6.0, 4.832] - lat = [50.0, 50.0, 50.0, 87.2, 75.0, 88.62, 87.24, 88.62] - pole_lt = [0.0, 12.0, 18.0, 1.260677777, 5.832, 6.0, 0.0, 5.832] - pole_lat = [88.0, 88.0, 88.0, 83.99, 87.24, 87.24, 87.24, 87.24] - self.comp = [0.0, 0.0, 0.0, 91.72024697182087, 8.67527923, 180.0, - 45.03325090532819, 150.9561733411] - for hemi in [-1, 1]: - for i, cval in enumerate(self.comp): + for i, self.comp in enumerate(self.pole_ang): # Set the input arguments - args = [lt[i], hemi * lat[i], pole_lt[i], hemi * pole_lat[i]] + args = [self.lt[i], hemi * self.lat[i], self.pole_lt[i], + hemi * self.pole_lat[i]] with self.subTest(args=args): # Calculate the pole angle self.out = vectors.calc_vec_pole_angle(*args) # Test the output - self.assertAlmostEqual(self.out, cval) + self.assertAlmostEqual(self.out, self.comp) return def test_calc_vec_pole_angle_array(self): """Test angle calc between base pole, data, and new pole for arrays.""" # Set locations designed to test no angle, flat lines, acute angles, # isocelese triangles, and oblique angles - lt = np.array([0.0, 24.0, 6.0, 21.22, 22.0, 6.0, 6.0, 4.832]) - lat = np.array([50.0, 50.0, 50.0, 87.2, 75.0, 88.62, 87.24, 88.62]) - pole_lt = np.array([0.0, 12.0, 18.0, 1.260677777, 5.832, 6.0, 0.0, - 5.832]) - pole_lat = np.array([88.0, 88.0, 88.0, 83.99, 87.24, 87.24, 87.24, - 87.24]) - self.comp = np.array([0.0, 0.0, 0.0, 91.72024697182087, 8.67527923, - 180.0, 45.03325090532819, 150.9561733411]) + self.lt = np.asarray(self.lt) + self.lat = np.asarray(self.lat) + self.pole_lt = np.asarray(self.pole_lt) + self.pole_lat = np.asarray(self.pole_lat) + self.comp = np.array(self.pole_ang) for hemi in [-1, 1]: with self.subTest(hemi=hemi): # Calculate the pole angle self.out = vectors.calc_vec_pole_angle( - lt, hemi * lat, pole_lt, hemi * pole_lat) + self.lt, hemi * self.lat, self.pole_lt, + hemi * self.pole_lat) # Test the output self.assertTrue(np.all(abs(self.out - self.comp) < 1e-5), @@ -122,10 +120,115 @@ def test_calc_vec_pole_angle_mixed(self): self.out = vectors.calc_vec_pole_angle(*args) # Test the output - self.assertTrue(self.out.shape == self.comp.shape) + self.assertTupleEqual(self.out.shape, self.comp.shape) self.assertTrue(np.all(abs(self.out - self.comp) < 1e-5), msg="{:} != {:}".format(self.out, self.comp)) return - - + + def test_define_pole_quadrants_float(self): + """Test the LT quadrant ID for the destination pole for floats.""" + # Set the expected pole quadrant output + self.comp = [1, 2, 2, 4, 1, 4, 2, 3] + + # Cycle through each of the local time pairs + for i, data_lt in enumerate(self.lt): + # Set the function arguements + args = [data_lt, self.pole_lt[i], self.pole_ang[i]] + + with self.subTest(args=args): + # Get the output + self.out = vectors.define_pole_quadrants(*args) + + # Test the integer quadrant assignment + self.assertEqual(self.out, self.comp[i]) + return + + def test_define_pole_quadrants_array(self): + """Test the LT quadrant ID for the destination pole for arrays.""" + # Set the expected pole quadrant output + self.comp = np.array([1, 2, 2, 4, 1, 4, 2, 3]) + + # Cycle through list-like or array-like inputs + for is_array in [True, False]: + # Set the function arguements + args = [self.lt, self.pole_lt, self.pole_ang] + + if is_array: + for i, arg in enumerate(args): + args[i] = np.asarray(arg) + + with self.subTest(is_array=is_array): + # Get the output + self.out = vectors.define_pole_quadrants(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(self.out == self.comp), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_define_pole_quadrants_mixed(self): + """Test the LT quadrant ID for the destination pole for mixed input.""" + # Cycle through the mixed inputs + for args, self.comp in [ + ([self.lt[0], np.asarray(self.pole_lt)[:2], + np.asarray(self.pole_ang)[:2]], np.array([1, 2])), + ([np.asarray(self.lt)[:2], self.pole_lt[0], self.pole_ang[0]], + np.array([1, 1]))]: + with self.subTest(args=args): + # Get the output + self.out = vectors.define_pole_quadrants(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(self.out == self.comp), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_define_pole_quadrants_neg_diff(self): + """Test the LT quadrant ID for the dest pole with very neg LT diffs.""" + self.lt = np.asarray(self.lt) + 48.0 + + # Cycle through float and array-like input + for is_float in [True, False]: + if is_float: + args = [self.lt[-1], self.pole_lt[-1], self.pole_ang[-1]] + self.comp = np.asarray(3) + else: + args = [self.lt, self.pole_lt, self.pole_ang] + self.comp = np.array([1, 2, 2, 4, 1, 4, 2, 3]) + + with self.subTest(args=args): + # Get the output + self.out = vectors.define_pole_quadrants(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(self.out == self.comp), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_define_pole_quadrants_large_diff(self): + """Test the LT quadrant ID for the dest pole with very pos LT diffs.""" + self.pole_lt = np.asarray(self.pole_lt) + 48.0 + + # Cycle through float and array-like input + for is_float in [True, False]: + if is_float: + args = [self.lt[-1], self.pole_lt[-1], self.pole_ang[-1]] + self.comp = np.asarray(3) + else: + args = [self.lt, self.pole_lt, self.pole_ang] + self.comp = np.array([1, 2, 2, 4, 1, 4, 2, 3]) + + with self.subTest(args=args): + # Get the output + self.out = vectors.define_pole_quadrants(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(self.out == self.comp), + msg="{:} != {:}".format(self.out, self.comp)) + return + From ab9cf9fd8acd82228997300eaaff64cdb620c62e Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 11:13:42 -0400 Subject: [PATCH 15/60] TST: added vector quadrant unit tests Added unit tests for the vector quadrant determination. --- ocbpy/tests/test_vectors.py | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py index 96f439bf..20b286d3 100644 --- a/ocbpy/tests/test_vectors.py +++ b/ocbpy/tests/test_vectors.py @@ -230,5 +230,67 @@ def test_define_pole_quadrants_large_diff(self): self.assertTrue(np.all(self.out == self.comp), msg="{:} != {:}".format(self.out, self.comp)) return + + def test_define_vect_quadrants_float(self): + """Test the vector direction quadrant ID for floats.""" + # Set the expected pole quadrant output + self.comp = {1: {1: 1, -1: 2}, -1: {1: 4, -1: 3}} + + # Cycle through each of the North directions + for vect_n in [1.0, 0.0, -1.0]: + nkey = 1 if vect_n >= 0.0 else -1 + + # Cycle through each of the East directions + for vect_e in [1.0, 0.0, -1.0]: + ekey = 1 if vect_e >= 0 else -1 + + with self.subTest(vect_n=vect_n, vect_e=vect_e): + # Get the output + self.out = vectors.define_vect_quadrants(vect_n, vect_e) + + # Test the integer quadrant assignment + self.assertEqual(self.out, self.comp[nkey][ekey]) + return + + def test_define_vect_quadrants_array(self): + """Test the vector direction quadrant ID for array-like input.""" + # Set the vector input and expected output + self.lt = [2.0, 0.0, -1.0, 3.0, -4.5, 0.0] # North vect component + self.lat = [3.0, 1.0, 3.5, -1.0, -2.0, 0.0] # East vect component + self.comp = np.array([1, 1, 4, 2, 3, 1]) + + # Cycle through list-like or array-like inputs + for is_array in [True, False]: + # Set the function arguements + args = [self.lt, self.lat] + + if is_array: + for i, arg in enumerate(args): + args[i] = np.asarray(arg) + + with self.subTest(is_array=is_array): + # Get the output + self.out = vectors.define_vect_quadrants(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(self.out == self.comp), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_define_vect_quadrants_mixed(self): + """Test the vector direction quadrant ID for mixed input.""" + # Cycle through the mixed inputs + for args, self.comp in [([6.0, [3.0, -3.5]], np.array([1, 2])), + ([[1.0, -1.0], 0.0], np.array([1, 4]))]: + with self.subTest(args=args): + # Get the output + self.out = vectors.define_vect_quadrants(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(self.out == self.comp), + msg="{:} != {:}".format(self.out, self.comp)) + return From f0a0075357013ad588ef5634bb53f951441b094a Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 12:17:27 -0400 Subject: [PATCH 16/60] BUG: improved vector list handling Improved `calc_dest_polar_angle` by ensuring list inputs can be used. --- ocbpy/vectors.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index 4bdae668..d9b02a44 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -317,16 +317,17 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): quad_range = np.arange(1, 5) # Test input - if not np.any(np.isin(pole_quad, quad_range)): + if not np.isin(pole_quad, quad_range).any(): raise ValueError("destination coordinate pole quadrant is undefined") - if not np.any(np.isin(vect_quad, quad_range)): + if not np.isin(vect_quad, quad_range).any(): raise ValueError("data vector quadrant is undefined") # Initialise the output and set the quadrant dictionary nan_mask = ~np.isnan(base_naz_angle) & ~np.isnan(pole_angle) - dest_naz_angle = np.full(shape=(base_naz_angle + pole_angle).shape, - fill_value=np.nan) + dest_naz_angle = np.full( + shape=(np.asarray(base_naz_angle) + np.asarray(pole_angle)).shape, + fill_value=np.nan) quads = {oquad: {vquad: (pole_quad == oquad) & (vect_quad == vquad) & nan_mask for vquad in quad_range} for oquad in quad_range} @@ -342,10 +343,11 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): # Calculate OCB polar angle based on the quadrants and other angles if np.any(abs_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = abs(base_naz_angle - pole_angle) + dest_naz_angle = abs(np.asarray(base_naz_angle) + - np.asarray(pole_angle)) else: - dest_naz_angle[abs_mask] = abs(base_naz_angle - - pole_angle)[abs_mask] + dest_naz_angle[abs_mask] = abs(np.asarray(base_naz_angle) + - np.asarray(pole_angle))[abs_mask] if np.any(add_mask): if len(dest_naz_angle.shape) == 0: @@ -353,29 +355,33 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): if dest_naz_angle > 180.0: dest_naz_angle = 360.0 - dest_naz_angle else: - dest_naz_angle[add_mask] = (pole_angle + base_naz_angle)[add_mask] + dest_naz_angle[add_mask] = ( + np.asarray(pole_angle) + np.asarray(base_naz_angle))[add_mask] lmask = (dest_naz_angle > 180.0) & add_mask if np.any(lmask): dest_naz_angle[lmask] = 360.0 - dest_naz_angle[lmask] if np.any(mpa_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = base_naz_angle - pole_angle + dest_naz_angle = np.asarray(base_naz_angle) - np.asarray(pole_angle) else: - dest_naz_angle[mpa_mask] = (base_naz_angle - pole_angle)[mpa_mask] + dest_naz_angle[mpa_mask] = ( + np.asarray(base_naz_angle) - np.asarray(pole_angle))[mpa_mask] if np.any(maa_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = pole_angle - base_naz_angle + dest_naz_angle = np.asarray(pole_angle) - np.asarray(base_naz_angle) else: - dest_naz_angle[maa_mask] = (pole_angle - base_naz_angle)[maa_mask] + dest_naz_angle[maa_mask] = ( + np.asarray(pole_angle) - np.asarray(base_naz_angle))[maa_mask] if np.any(cir_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = 360.0 - base_naz_angle - pole_angle + dest_naz_angle = 360.0 - np.asarray(base_naz_angle) - np.asarray( + pole_angle) else: - dest_naz_angle[cir_mask] = (360.0 - base_naz_angle - pole_angle)[ - cir_mask] + dest_naz_angle[cir_mask] = (360.0 - np.asarray( + base_naz_angle) - np.asarray(pole_angle))[cir_mask] return dest_naz_angle From a78f3d3ef4de41e88788913434d4cffd059e9abb Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 12:18:45 -0400 Subject: [PATCH 17/60] TST: added unit tests for `calc_dest_polar_angle` Added unit tests for the vector function `calc_dest_polar_angle`. --- ocbpy/tests/test_vectors.py | 103 +++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py index 20b286d3..787138d5 100644 --- a/ocbpy/tests/test_vectors.py +++ b/ocbpy/tests/test_vectors.py @@ -292,5 +292,106 @@ def test_define_vect_quadrants_mixed(self): self.assertTrue(np.all(self.out == self.comp), msg="{:} != {:}".format(self.out, self.comp)) return - + + def test_calc_dest_polar_angle_float(self): + """Test the north azimuth angle calculation for float inputs.""" + # Set the expected pole quadrant output + self.comp = {1: {1: 138.11957692472973, 2: 148.11957692472973, + 3: 148.11957692472973, 4: -138.11957692472973}, + 2: {1: 148.11957692472973, 2: 138.11957692472973, + 3: -138.11957692472973, 4: 148.11957692472973}, + 3: {1: 148.11957692472973, 2: 138.11957692472973, + 3: 138.11957692472973, 4: 211.88042307527027}, + 4: {1: 138.11957692472973, 2: 148.11957692472973, + 3: 211.88042307527027, 4: 138.11957692472973}} + + # Cycle through each of the pole quadrants + for pole_quad in np.arange(1, 5, 1): + # Cycle through each of the vector quadrants + for vect_quad in np.arange(1, 5, 1): + with self.subTest(pole_quad=pole_quad, vect_quad=vect_quad): + # Get the output + self.out = vectors.calc_dest_polar_angle( + pole_quad, vect_quad, 5.0, self.pole_ang[-1]) + + # Test the integer quadrant assignment + self.assertEqual(self.out, self.comp[pole_quad][vect_quad]) + return + + def test_calc_dest_polar_angle_array(self): + """Test the north azimuth angle calculation for array-like inputs.""" + # Set the expected inputs and outputs + self.lt = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + self.lat = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] + self.pole_lt = list(np.linspace(0.0, 360.0, num=len(self.lt))) + self.pole_ang = list(self.pole_ang) + list(self.pole_ang) + self.comp = np.array([0.0, 24.0, 48.0, -19.72024697, 104.67527923, 60.0, + 98.96674909, 48.88042308, 168.0, -216.0, 240.0, + 4.27975303, -279.32472077, -132.0, -21.03325091, + 216.88042308]) + + # Cycle through list-like or array-like inputs + for is_array in [True, False]: + # Set the function arguements + args = [self.lt, self.lat, self.pole_lt, self.pole_ang] + + if is_array: + for i, arg in enumerate(args): + args[i] = np.asarray(arg) + + with self.subTest(is_array=is_array): + # Get the output + self.out = vectors.calc_dest_polar_angle(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(abs(self.out - self.comp) < 1.0e-5), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_calc_dest_polar_angle_mixed(self): + """Test the north azimuth angle calculation for mixed inputs.""" + self.lt = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + self.lat = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] + self.pole_lt = list(np.linspace(0.0, 360.0, num=len(self.lt))) + self.pole_ang = list(self.pole_ang) + list(self.pole_ang) + self.comp = np.array([0.0, 24.0, 48.0, -19.72024697, 104.67527923, 60.0, + 98.96674909, 48.88042308, 168.0, -216.0, 240.0, + 4.27975303, -279.32472077, -132.0, -21.03325091, + 216.88042308]) + + # Cycle through the mixed inputs + for args, self.comp in [([1, [2, 3], [0.0, 24.0], 0.0], + np.array([0.0, 24.0])), + ([[1, 4], 4, [72, 360], [91.720246, 143.11957]], + np.array([-19.720246, 216.88043])), + ([[1, 2], [3, 4], 5.0, [91.720246, 143.11957]], + np.array([96.720246, 148.11957])), + ([[3, 4], [2, 1], [0.0, 90.0], 10.0], + np.array([10.0, -80.0]))]: + with self.subTest(args=args): + # Get the output + self.out = vectors.calc_dest_polar_angle(*args) + + # Test the integer quadrant assignment + self.assertTupleEqual(self.out.shape, self.comp.shape) + self.assertTrue(np.all(abs(self.out - self.comp) < 1.0e-5), + msg="{:} != {:}".format(self.out, self.comp)) + return + + def test_calc_dest_polar_angle_bad_pole_quad(self): + """Test the north azimuth angle calculation with undefined pole quad.""" + + self.comp = "destination coordinate pole quadrant is undefined" + with self.assertRaisesRegex(ValueError, self.comp): + ocbpy.vectors.calc_dest_polar_angle(0, 1, 5.0, 5.0) + return + + def test_calc_dest_polar_angle_bad_vect_quad(self): + """Test the north azimuth angle calculation with undefined vect quad.""" + + self.comp = "data vector quadrant is undefined" + with self.assertRaisesRegex(ValueError, self.comp): + ocbpy.vectors.calc_dest_polar_angle(1, 0, 5.0, 5.0) + return From 68385e68d01fa2aba8de8a1ed4eb66ff1da8ca73 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 16:33:57 -0400 Subject: [PATCH 18/60] BUG: improved mixed input handling Improved array/float handling in the `adjust_vector` and `calc_dest_polar_angle` functions. --- ocbpy/vectors.py | 119 ++++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index d9b02a44..8feb121f 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -323,11 +323,14 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): if not np.isin(vect_quad, quad_range).any(): raise ValueError("data vector quadrant is undefined") + # Cast the necessary variables + base_naz_angle = np.asarray(base_naz_angle) + pole_angle = np.asarray(pole_angle) + # Initialise the output and set the quadrant dictionary nan_mask = ~np.isnan(base_naz_angle) & ~np.isnan(pole_angle) - dest_naz_angle = np.full( - shape=(np.asarray(base_naz_angle) + np.asarray(pole_angle)).shape, - fill_value=np.nan) + dest_naz_angle = np.full(shape=(base_naz_angle + pole_angle).shape, + fill_value=np.nan) quads = {oquad: {vquad: (pole_quad == oquad) & (vect_quad == vquad) & nan_mask for vquad in quad_range} for oquad in quad_range} @@ -343,11 +346,10 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): # Calculate OCB polar angle based on the quadrants and other angles if np.any(abs_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = abs(np.asarray(base_naz_angle) - - np.asarray(pole_angle)) + dest_naz_angle = abs(base_naz_angle - pole_angle) else: - dest_naz_angle[abs_mask] = abs(np.asarray(base_naz_angle) - - np.asarray(pole_angle))[abs_mask] + dest_naz_angle[abs_mask] = abs(base_naz_angle + - pole_angle)[abs_mask] if np.any(add_mask): if len(dest_naz_angle.shape) == 0: @@ -355,33 +357,29 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): if dest_naz_angle > 180.0: dest_naz_angle = 360.0 - dest_naz_angle else: - dest_naz_angle[add_mask] = ( - np.asarray(pole_angle) + np.asarray(base_naz_angle))[add_mask] + dest_naz_angle[add_mask] = (pole_angle + base_naz_angle)[add_mask] lmask = (dest_naz_angle > 180.0) & add_mask if np.any(lmask): dest_naz_angle[lmask] = 360.0 - dest_naz_angle[lmask] if np.any(mpa_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = np.asarray(base_naz_angle) - np.asarray(pole_angle) + dest_naz_angle = base_naz_angle - pole_angle else: - dest_naz_angle[mpa_mask] = ( - np.asarray(base_naz_angle) - np.asarray(pole_angle))[mpa_mask] + dest_naz_angle[mpa_mask] = (base_naz_angle - pole_angle)[mpa_mask] if np.any(maa_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = np.asarray(pole_angle) - np.asarray(base_naz_angle) + dest_naz_angle = pole_angle - base_naz_angle else: - dest_naz_angle[maa_mask] = ( - np.asarray(pole_angle) - np.asarray(base_naz_angle))[maa_mask] + dest_naz_angle[maa_mask] = (pole_angle - base_naz_angle)[maa_mask] if np.any(cir_mask): if len(dest_naz_angle.shape) == 0: - dest_naz_angle = 360.0 - np.asarray(base_naz_angle) - np.asarray( - pole_angle) + dest_naz_angle = 360.0 - base_naz_angle - pole_angle else: - dest_naz_angle[cir_mask] = (360.0 - np.asarray( - base_naz_angle) - np.asarray(pole_angle))[cir_mask] + dest_naz_angle[cir_mask] = (360.0 - base_naz_angle + - pole_angle)[cir_mask] return dest_naz_angle @@ -424,6 +422,10 @@ def calc_dest_vec_sign(pole_quad, vect_quad, base_naz_angle, pole_angle, """ quad_range = np.arange(1, 5) + # Cast the input + base_naz_angle = np.asarray(base_naz_angle) + pole_angle = np.asarray(pole_angle) + # Test input if not np.any(np.isin(pole_quad, quad_range)): raise ValueError("destination coordinate pole quadrant is undefined") @@ -541,10 +543,24 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, latitude """ - # Initialize the output - dest_n = np.full(shape=np.asarray(vect_n).shape, fill_value=np.nan) - dest_e = np.full(shape=np.asarray(vect_e).shape, fill_value=np.nan) - dest_z = np.full(shape=np.asarray(vect_z).shape, fill_value=np.nan) + # Ensure the input is array-like + vect_lt = np.asarray(vect_lt) + vect_lat = np.asarray(vect_lat) + vect_n = np.asarray(vect_n) + vect_e = np.asarray(vect_e) + vect_z = np.asarray(vect_z) + vect_quad = np.asarray(vect_quad) + pole_lt = np.asarray(pole_lt) + pole_lat = np.asarray(pole_lat) + pole_angle = np.asarray(pole_angle) + pole_quad = np.asarray(pole_quad) + + # Initialize the output, ensuring it is the same shape + out_shape = (vect_lt + vect_lat + vect_n + vect_e + vect_z + vect_quad + + pole_lt + pole_lat + pole_angle + pole_quad).shape + dest_n = np.full(shape=out_shape, fill_value=np.nan) + dest_e = np.full(shape=out_shape, fill_value=np.nan) + dest_z = np.full(shape=out_shape, fill_value=np.nan) # Determine the special case assignments zero_mask = (vect_n == 0.0) & (vect_e == 0.0) @@ -553,10 +569,10 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, # There's nothing to adjust if there is no magnitude if np.any(zero_mask): - if len(zero_mask.shape) == 0: - dest_n = np.zeros(shape=dest_n.shape) - dest_e = np.zeros(shape=dest_e.shape) - dest_z = np.zeros(shape=dest_z.shape) + if len(out_shape) == 0: + dest_n = 0.0 + dest_e = 0.0 + dest_z = 0.0 else: dest_n[zero_mask] = 0.0 dest_e[zero_mask] = 0.0 @@ -564,14 +580,25 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, # The measurement is aligned with the base and destination poles if np.any(ns_mask): - if len(vect_n.shape) == 0: - dest_n = np.full(shape=dest_n.shape, fill_value=vect_n) - dest_e = np.full(shape=dest_e.shape, fill_value=vect_e) - dest_z = np.full(shape=dest_z.shape, fill_value=vect_z) + if len(out_shape) == 0: + dest_n = float(vect_n) + dest_e = float(vect_e) + dest_z = float(vect_z) else: - dest_n[ns_mask] = vect_n[ns_mask] - dest_e[ns_mask] = vect_e[ns_mask] - dest_z[ns_mask] = vect_z[ns_mask] + if len(vect_n.shape) == 0: + dest_n[ns_mask] = vect_n + else: + dest_n[ns_mask] = vect_n[ns_mask] + + if len(vect_e.shape) == 0: + dest_e[ns_mask] = vect_e + else: + dest_e[ns_mask] = vect_e[ns_mask] + + if len(vect_z.shape) == 0: + dest_z[ns_mask] = vect_z + else: + dest_z[ns_mask] = vect_z[ns_mask] # Determine if the measurement is on or between the poles. This does # not affect the vertical direction @@ -579,7 +606,7 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, vect_lat, pole_lat, where=~np.isnan(vect_lat)) & ~np.isnan(vect_lat) if np.any(sign_mask): - if dest_n.shape == (): + if len(out_shape) == 0: dest_n *= -1.0 dest_e *= -1.0 else: @@ -599,14 +626,22 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, # Get the unscaled 2D vector magnitude and calculate the AACGM north # azimuth in degrees - if len(vect_n.shape) == 0: + if len(vect_n.shape) == 0 and len(vect_e.shape) == 0: vmag = np.sqrt(vect_n**2 + vect_e**2) base_naz_angle = np.degrees(np.arccos(vect_n / vmag)) else: - vmag = np.sqrt(vect_n[norm_mask]**2 + vect_e[norm_mask]**2) base_naz_angle = np.full(shape=norm_mask.shape, fill_value=np.nan) - base_naz_angle[norm_mask] = np.degrees(np.arccos(vect_n[norm_mask] - / vmag)) + + if len(vect_n.shape) == 0: + vmag = np.sqrt(vect_n**2 + vect_e[norm_mask]**2) + base_naz_angle[norm_mask] = np.degrees(np.arccos(vect_n / vmag)) + else: + if len(vect_e.shape) == 0: + vmag = np.sqrt(vect_n[norm_mask]**2 + vect_e**2) + else: + vmag = np.sqrt(vect_n[norm_mask]**2 + vect_e[norm_mask]**2) + base_naz_angle[norm_mask] = np.degrees( + np.arccos(vect_n[norm_mask] / vmag)) # Get the destination coordinate system north azimuth in radians dest_naz_angle = np.radians(calc_dest_polar_angle( @@ -637,11 +672,11 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, # Calculate the vector components if len(vmag.shape) == 0: if len(dest_naz_angle.shape) == 0: - dest_n = np.full(shape=dest_n.shape, fill_value=( + dest_n = np.full(shape=out_shape, fill_value=( vsigns['north'] * vmag * np.cos(dest_naz_angle))) - dest_e = np.full(shape=dest_e.shape, fill_value=( + dest_e = np.full(shape=out_shape, fill_value=( vsigns['east'] * vmag * np.sin(dest_naz_angle))) - dest_z = np.full(shape=dest_z.shape, fill_value=vz) + dest_z = np.full(shape=out_shape, fill_value=vz) else: nval = vsigns['north'][norm_mask] * vmag * np.cos( dest_naz_angle)[norm_mask] From 0d97af61961118aadd0a1ad33da7baefa444c889 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 16:38:42 -0400 Subject: [PATCH 19/60] STY: removed whitespace Removed extra whitespace. --- ocbpy/ocb_scaling.py | 6 +++--- ocbpy/tests/test_ocb_scaling.py | 2 +- ocbpy/vectors.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 2798413f..2fc9fa63 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -174,7 +174,7 @@ def __init__(self, dat_ind, ocb_ind, lat, lt, height=350.0, if set_mag: # Set the magnitude if the deprecated kwarg was not supplied self.vect_mag = vect_mag - + # Test the coordinate systems for valid options self._test_coords() @@ -299,7 +299,7 @@ def __str__(self): self.vect_coord, ": {:.3g} [".format(self.vect_mag[i]), "{:.3g}, {:.3g}".format(self.vect_n[i], self.vect_e[i]), ", {:.3g}] {:d}".format(self.vect_z[i], - self.dat_ind[i])]) + self.dat_ind[i])]) if not np.isnan(mag): vec_line = "".join([ vec_line, "\n OCB: {:.3g} [".format(mag), @@ -912,7 +912,7 @@ def calc_ocb_polar_angle(self): " will be removed in version 0.4.1+. Instead, ", "use `ocbpy.vectors.calc_dest_polar_angle`."]), DeprecationWarning, stacklevel=2) - + # Test input if np.all(np.isnan(self.aacgm_naz)): raise ValueError("AACGM North polar angle undefined") diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index b941c2d1..cc0212d0 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -1336,7 +1336,7 @@ def test_scale_vec_pole_angle_zero(self): """Test the calculation of the OCB vector sign with no pole angle.""" self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.vdata.set_ocb(self.ocb) - + # If the OCB pole is closer to the AACGM pole than the vector, set # the pole angle to zero deg. Otherwise, set it to 180.0 deg self.vdata.pole_angle = numpy.zeros(shape=self.vargs[2].shape) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index 8feb121f..a28212a1 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -554,7 +554,7 @@ def adjust_vector(vect_lt, vect_lat, vect_n, vect_e, vect_z, vect_quad, pole_lat = np.asarray(pole_lat) pole_angle = np.asarray(pole_angle) pole_quad = np.asarray(pole_quad) - + # Initialize the output, ensuring it is the same shape out_shape = (vect_lt + vect_lat + vect_n + vect_e + vect_z + vect_quad + pole_lt + pole_lat + pole_angle + pole_quad).shape From e0bf2cfff02e0a7ead39ed340fda69d1e1060c6e Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 16:40:07 -0400 Subject: [PATCH 20/60] TST: added `adjust_vector` unit tests Improved test class setup/teardown and added unit tests for the `adjust_vector` function. --- ocbpy/tests/test_vectors.py | 295 ++++++++++++++++++++++++++++++++---- 1 file changed, 266 insertions(+), 29 deletions(-) diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py index 787138d5..3e8439e3 100644 --- a/ocbpy/tests/test_vectors.py +++ b/ocbpy/tests/test_vectors.py @@ -18,17 +18,23 @@ def setUp(self): """Initialize the test class.""" self.lt = [0.0, 24.0, 6.0, 21.22, 22.0, 6.0, 6.0, 0.0] self.lat = [50.0, 50.0, 50.0, 87.2, 75.0, 88.62, 87.24, 89.0] + self.vect_n = [2.0, 0.0, -1.0, 3.0, -4.5, 0.0, 1.0, -1.0] + self.vect_e = [3.0, 1.0, 3.5, -1.0, -2.0, 0.0, -1.0, 1.0] + self.vect_quad = [1, 1, 4, 2, 3, 1, 2, 4] self.pole_lt = [0.0, 12.0, 18.0, 1.260677777, 5.832, 6.0, 0.0, 22.0] self.pole_lat = [88.0, 88.0, 88.0, 83.99, 87.24, 87.24, 87.24, 85.0] self.pole_ang = [0.0, 0.0, 0.0, 91.72024697182087, 8.67527923, 180.0, 45.03325090532819, 143.11957692472973] + self.pole_quad = [1, 2, 2, 4, 1, 4, 2, 3] self.out = None self.comp = None return def tearDown(self): """Clean up the test class.""" - del self.lt, self.lat, self.out, self.comp + del self.lt, self.lat, self.vect_n, self.vect_e, self.vect_quad + del self.pole_lt, self.pole_lat, self.pole_ang, self.pole_quad + del self.out, self.comp return def test_get_pole_loc_float(self): @@ -53,7 +59,7 @@ def test_get_pole_loc_array(self): rad = [5.0, 4.0, 3.0, 2.0, 1.0, 6.0, 0.0] self.comp = (np.array([0.0, 6.0, 12.0, 18.0, 0.0, 6.0]), np.array([85.0, 86.0, 87.0, 88.0, 89.0, 84.0, 90.0])) - + # Convert the input self.out = vectors.get_pole_loc(phi, rad) @@ -127,8 +133,6 @@ def test_calc_vec_pole_angle_mixed(self): def test_define_pole_quadrants_float(self): """Test the LT quadrant ID for the destination pole for floats.""" - # Set the expected pole quadrant output - self.comp = [1, 2, 2, 4, 1, 4, 2, 3] # Cycle through each of the local time pairs for i, data_lt in enumerate(self.lt): @@ -140,13 +144,13 @@ def test_define_pole_quadrants_float(self): self.out = vectors.define_pole_quadrants(*args) # Test the integer quadrant assignment - self.assertEqual(self.out, self.comp[i]) + self.assertEqual(self.out, self.pole_quad[i]) return def test_define_pole_quadrants_array(self): """Test the LT quadrant ID for the destination pole for arrays.""" # Set the expected pole quadrant output - self.comp = np.array([1, 2, 2, 4, 1, 4, 2, 3]) + self.comp = np.array(self.pole_quad) # Cycle through list-like or array-like inputs for is_array in [True, False]: @@ -196,7 +200,7 @@ def test_define_pole_quadrants_neg_diff(self): self.comp = np.asarray(3) else: args = [self.lt, self.pole_lt, self.pole_ang] - self.comp = np.array([1, 2, 2, 4, 1, 4, 2, 3]) + self.comp = np.array(self.pole_quad) with self.subTest(args=args): # Get the output @@ -219,7 +223,7 @@ def test_define_pole_quadrants_large_diff(self): self.comp = np.asarray(3) else: args = [self.lt, self.pole_lt, self.pole_ang] - self.comp = np.array([1, 2, 2, 4, 1, 4, 2, 3]) + self.comp = np.array(self.pole_quad) with self.subTest(args=args): # Get the output @@ -254,15 +258,13 @@ def test_define_vect_quadrants_float(self): def test_define_vect_quadrants_array(self): """Test the vector direction quadrant ID for array-like input.""" - # Set the vector input and expected output - self.lt = [2.0, 0.0, -1.0, 3.0, -4.5, 0.0] # North vect component - self.lat = [3.0, 1.0, 3.5, -1.0, -2.0, 0.0] # East vect component - self.comp = np.array([1, 1, 4, 2, 3, 1]) + # Set the vector quadrant output + self.comp = np.array(self.vect_quad) # Cycle through list-like or array-like inputs for is_array in [True, False]: # Set the function arguements - args = [self.lt, self.lat] + args = [self.vect_n, self.vect_e] if is_array: for i, arg in enumerate(args): @@ -321,9 +323,9 @@ def test_calc_dest_polar_angle_float(self): def test_calc_dest_polar_angle_array(self): """Test the north azimuth angle calculation for array-like inputs.""" # Set the expected inputs and outputs - self.lt = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] - self.lat = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] - self.pole_lt = list(np.linspace(0.0, 360.0, num=len(self.lt))) + self.pole_quad = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + self.vect_quad = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] + self.pole_lt = list(np.linspace(0.0, 360.0, num=len(self.pole_quad))) self.pole_ang = list(self.pole_ang) + list(self.pole_ang) self.comp = np.array([0.0, 24.0, 48.0, -19.72024697, 104.67527923, 60.0, 98.96674909, 48.88042308, 168.0, -216.0, 240.0, @@ -333,7 +335,7 @@ def test_calc_dest_polar_angle_array(self): # Cycle through list-like or array-like inputs for is_array in [True, False]: # Set the function arguements - args = [self.lt, self.lat, self.pole_lt, self.pole_ang] + args = [self.pole_quad, self.vect_quad, self.pole_lt, self.pole_ang] if is_array: for i, arg in enumerate(args): @@ -351,15 +353,6 @@ def test_calc_dest_polar_angle_array(self): def test_calc_dest_polar_angle_mixed(self): """Test the north azimuth angle calculation for mixed inputs.""" - self.lt = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] - self.lat = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] - self.pole_lt = list(np.linspace(0.0, 360.0, num=len(self.lt))) - self.pole_ang = list(self.pole_ang) + list(self.pole_ang) - self.comp = np.array([0.0, 24.0, 48.0, -19.72024697, 104.67527923, 60.0, - 98.96674909, 48.88042308, 168.0, -216.0, 240.0, - 4.27975303, -279.32472077, -132.0, -21.03325091, - 216.88042308]) - # Cycle through the mixed inputs for args, self.comp in [([1, [2, 3], [0.0, 24.0], 0.0], np.array([0.0, 24.0])), @@ -384,7 +377,7 @@ def test_calc_dest_polar_angle_bad_pole_quad(self): self.comp = "destination coordinate pole quadrant is undefined" with self.assertRaisesRegex(ValueError, self.comp): - ocbpy.vectors.calc_dest_polar_angle(0, 1, 5.0, 5.0) + vectors.calc_dest_polar_angle(0, 1, 5.0, 5.0) return def test_calc_dest_polar_angle_bad_vect_quad(self): @@ -392,6 +385,250 @@ def test_calc_dest_polar_angle_bad_vect_quad(self): self.comp = "data vector quadrant is undefined" with self.assertRaisesRegex(ValueError, self.comp): - ocbpy.vectors.calc_dest_polar_angle(1, 0, 5.0, 5.0) + vectors.calc_dest_polar_angle(1, 0, 5.0, 5.0) + return + + def test_calc_dest_vec_sign_float(self): + """Test the vector sign calculation for float inputs.""" + # Set the expected vector sign output + self.comp = { + 1: {1: {'north': 1, 'east': -1}, 2: {'north': -1, 'east': -1}, + 3: {'north': -1, 'east': -1}, 4: {'north': 1, 'east': 1}}, + 2: {1: {'north': -1, 'east': 1}, 2: {'north': 1, 'east': 1}, + 3: {'north': 1, 'east': -1}, 4: {'north': -1, 'east': 1}}, + 3: {1: {'north': -1, 'east': 1}, 2: {'north': -1, 'east': 1}, + 3: {'north': 1, 'east': 1}, 4: {'north': -1, 'east': -1}}, + 4: {1: {'north': -1, 'east': -1}, 2: {'north': -1, 'east': -1}, + 3: {'north': -1, 'east': 1}, 4: {'north': 1, 'east': -1}}} + + # Cycle through each of the pole quadrants + for pole_quad in np.arange(1, 5, 1): + # Cycle through each of the vector quadrants + for vect_quad in np.arange(1, 5, 1): + for nout in [True, False]: + for eout in [True, False]: + with self.subTest( + pole_quad=pole_quad, vect_quad=vect_quad, + north_out=nout, east_out=eout): + # Get the output + self.out = vectors.calc_dest_vec_sign( + pole_quad, vect_quad, 5.0, self.pole_ang[-1], + north=nout, east=eout) + + # Update the comparison data + sub_comp = dict(self.comp[pole_quad][vect_quad]) + if not nout: + sub_comp['north'] = 0 + if not eout: + sub_comp['east'] = 0 + + # Test the integer quadrant assignment + self.assertDictEqual(self.out, sub_comp) + return + + def test_calc_dest_vec_sign_array(self): + """Test the vector sign calculation for array inputs.""" + # Set the expected vector sign input and output + self.pole_quad = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + self.vect_quad = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] + self.pole_lt = list(np.linspace(0.0, 360.0, num=len(self.pole_quad))) + self.pole_ang = list(self.pole_ang) + list(self.pole_ang) + self.comp = { + 'north': np.array([1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, + 1, 1]), + 'east': np.array([1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, + 1, 1])} + + # Cycle through list-like or array-like inputs + for is_array in [True, False]: + # Set the function arguements + args = [self.pole_quad, self.vect_quad, self.pole_lt, self.pole_ang] + + if is_array: + for i, arg in enumerate(args): + args[i] = np.asarray(arg) + + # Cycle through different output types + for nout in [True, False]: + for eout in [True, False]: + # Update the comparison data + sub_comp = {key: np.array(self.comp[key]) + for key in self.comp.keys()} + if not nout: + sub_comp['north'] *= 0 + if not eout: + sub_comp['east'] *= 0 + + with self.subTest(is_array=is_array, north=nout, east=eout): + # Get the output + self.out = vectors.calc_dest_vec_sign( + *args, north=nout, east=eout) + + # Test the vector sign assignment + self.assertListEqual(list(self.out.keys()), + list(sub_comp.keys())) + for key in self.out.keys(): + self.assertTrue( + np.all(self.out[key] == sub_comp[key]), + msg="{:}: {:} != {:}".format( + key, self.out[key], sub_comp[key])) + return + + def test_calc_dest_vec_sign_mixed(self): + """Test the vector sign calculation for mixed inputs.""" + + # Cycle through the mixed inputs + for args, self.comp in [([1, [2, 3], [0.0, 24.0], 0.0], + {'north': np.array([1, -1]), + 'east': np.array([-1, -1])}), + ([[1, 4], 4, [72, 360], [91.720246, 143.11957]], + {'north': np.array([1, 1]), + 'east': np.array([1, 1])}), + ([[1, 2], [3, 4], 5.0, [91.720246, 143.11957]], + {'north': np.array([-1, -1]), + 'east': np.array([-1, 1])}), + ([[3, 4], [2, 1], [0.0, 90.0], 10.0], + {'north': np.array([1, 1]), + 'east': np.array([1, -1])})]: + with self.subTest(args=args): + # Get the output + self.out = vectors.calc_dest_vec_sign(*args, north=True, + east=True) + + # Test the vector sign assignment + self.assertListEqual(list(self.out.keys()), + list(self.comp.keys())) + for key in self.out.keys(): + self.assertTrue(np.all(self.out[key] == self.comp[key]), + msg="{:}: {:} != {:}".format( + key, self.out[key], self.comp[key])) + return + + def test_calc_dest_vec_sign_bad_pole_quad(self): + """Test the vector sign calculation with an undefined pole quad.""" + + self.comp = "destination coordinate pole quadrant is undefined" + with self.assertRaisesRegex(ValueError, self.comp): + vectors.calc_dest_vec_sign(0, 1, 5.0, 5.0) + return + + def test_calc_dest_vec_sign_bad_vect_quad(self): + """Test the vector sign calculation with an undefined vect quad.""" + + self.comp = "data vector quadrant is undefined" + with self.assertRaisesRegex(ValueError, self.comp): + vectors.calc_dest_vec_sign(1, 0, 5.0, 5.0) + return + + def test_adjust_vector_float(self): + """Test the vector adjustment with float inputs.""" + self.comp = (np.array([2.0, 0.0, -1.0, -1.0896077, -4.75018438, 0.0, + 1.41421332, 0.19974281]), + np.array([3.0, 1.0, 3.5, -2.96862848, -1.29836371, 0.0, + 0.000820721509, -1.40003672]), + np.array([1, 1, 1, 1, 1, 1, 1, 1])) + + # Cycle through all possible inputs + for i, vect_lt in enumerate(self.lt): + args = [vect_lt, self.lat[i], self.vect_n[i], self.vect_e[i], 1.0, + self.vect_quad[i], self.pole_lt[i], self.pole_lat[i], + self.pole_ang[i], self.pole_quad[i]] + + with self.subTest(args=args): + # Get the output + self.out = vectors.adjust_vector(*args) + + # Test the output + self.assertTrue(len(self.out), 3) + self.assertAlmostEqual(float(self.out[0]), self.comp[0][i], + msg="bad north vector value") + self.assertAlmostEqual(float(self.out[1]), self.comp[1][i], + msg="bad east vector value") + self.assertAlmostEqual(float(self.out[2]), self.comp[2][i], + msg="bad verticle vector value") + return + + def test_adjust_vector_array(self): + """Test the vector adjustment with array-like inputs.""" + self.comp = (np.array([2.0, 0.0, -1.0, -1.0896077, -4.75018438, 0.0, + 1.41421332, 0.19974281]), + np.array([3.0, 1.0, 3.5, -2.96862848, -1.29836371, 0.0, + 0.000820721509, -1.40003672]), + np.array([1, 1, 1, 1, 1, 1, 1, 1])) + + # Cycle through list-like or array-like inputs + for is_array in [True, False]: + # Set the function arguements + args = [self.lt, self.lat, self.vect_n, self.vect_e, + np.ones(shape=len(self.lt)), self.vect_quad, self.pole_lt, + self.pole_lat, self.pole_ang, self.pole_quad] + + if is_array: + for i, arg in enumerate(args): + args[i] = np.asarray(arg) + + with self.subTest(is_array=is_array): + # Get the output + self.out = vectors.adjust_vector(*args) + + # Test the output + self.assertTrue(len(self.out), 3) + self.assertTrue(np.all(abs(self.out[0] - self.comp[0]) < 1e-5), + msg="bad north values: {:} != {:}".format( + self.out[0], self.comp[0])) + self.assertTrue(np.all(abs(self.out[1] - self.comp[1]) < 1e-5), + msg="bad east values: {:} != {:}".format( + self.out[1], self.comp[1])) + self.assertTrue(np.all(abs(self.out[2] - self.comp[2]) < 1e-5), + msg="bad vertical values: {:} != {:}".format( + self.out[2], self.comp[2])) + return + + def test_adjust_vector_mixed(self): + """Test the vector adjustment with mixed inputs.""" + # Set the base input and output + args = [np.asarray(self.lt), np.asarray(self.lat), + np.asarray(self.vect_n), np.asarray(self.vect_e), + np.ones(shape=len(self.lt)), np.asarray(self.vect_quad), + np.asarray(self.pole_lt), np.asarray(self.pole_lat), + np.asarray(self.pole_ang), np.asarray(self.pole_quad)] + + self.comp = (np.array([2.0, 0.0, -1.0, -1.0896077, -4.75018438, 0.0, + 1.41421332, 0.19974281]), + np.array([3.0, 1.0, 3.5, -2.96862848, -1.29836371, 0.0, + 0.000820721509, -1.40003672]), + np.array([1, 1, 1, 1, 1, 1, 1, 1])) + + # Cycle through compinations of float/array inputs + for ipos, ival, islice in [(0, 0, [0, -1]), (1, 0, [0, 1, 2]), + (2, 1, [1, 5]), (3, 1, [1, -1]), + (4, 0, slice(None)), (5, 0, [0, 1, 5]), + (6, 0, [0, 6]), (7, 5, [5, 6]), + (8, 0, [0, 1, 2]), (9, 3, [3, 5])]: + # Assign the mixed inputs + vect_args = list() + for i, arg in enumerate(args): + if i == ipos: + vect_args.append(arg[ival]) + else: + vect_args.append(arg[islice]) + + with self.subTest(args=vect_args): + # Get the output + self.out = vectors.adjust_vector(*vect_args) + + # Test the output + self.assertTrue(len(self.out), 3) + self.assertTrue( + np.all(abs(self.out[0] - self.comp[0][islice]) < 1e-5), + msg="bad north values: {:} != {:}".format( + self.out[0], self.comp[0][islice])) + self.assertTrue( + np.all(abs(self.out[1] - self.comp[1][islice]) < 1e-5), + msg="bad east values: {:} != {:}".format( + self.out[1], self.comp[1][islice])) + self.assertTrue( + np.all(abs(self.out[2] - self.comp[2][islice]) < 1e-5), + msg="bad vertical values: {:} != {:}".format( + self.out[2], self.comp[2][islice])) return - From acd88f0d13bf74b30a8d37c8860e296d90561c05 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 25 Apr 2024 17:04:03 -0400 Subject: [PATCH 21/60] BUG: added access to deprecated attributes Added access to deprecated attributes as properties or normal attributes (based on their previous type). --- ocbpy/ocb_scaling.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 2fc9fa63..b3ccd7f5 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -351,6 +351,35 @@ def __setattr__(self, name, value): super(VectorData, self).__setattr__(name, out_val) return + # TODO(#133): remove after old attributes are deprecated + def __getattr__(self, name, **kwargs): + """Get attributes, allowing access to deprecated names. + + Parameters + ---------- + name : str + Attribute name to be accessed from VectorData + + Returns + ------- + value : any + Value assigned to the attribute + + """ + # Define the deprecated attributes that are not properties + dep_pairs = {'aacgm_n': 'vect_n', 'aacgm_e': 'vect_e', + 'aacgm_z': 'vect_z'} + if name in dep_pairs.keys(): + warnings.warn("".join([name, ' has been replaced with ', + dep_pairs[name], '. Old attribute will be ', + 'removed in version 0.4.1+.']), + DeprecationWarning, stacklevel=2) + name = dep_pairs[name] + + # Use Object to avoid recursion + value = super(VectorData, self).__getattribute__(name) + return value + def _ocb_attr_setter(self, ocb_name, ocb_val): """Set OCB attributes. @@ -538,6 +567,15 @@ def vect_mag(self, vect_mag): self._vect_mag = vect_mag return + @property + def aacgm_mag(self): + """Deprecated magnitude of the vector(s).""" + # TODO(#133): remove after old attributes are deprecated + warnings.warn("".join(['`aacgm_mag` has been replaced with `vect_mag`,', + ' and will be removed in version 0.4.1+.']), + DeprecationWarning, stacklevel=2) + return self.vect_mag + @property def dat_ind(self): """Data index(es).""" @@ -645,6 +683,15 @@ def lat(self, lat): self._dat_attr_setter('_lat', lat) return + @property + def aacgm_lat(self): + """Deprecated magnitude of the vector(s).""" + # TODO(#133): remove after old attributes are deprecated + warnings.warn("".join(['`aacgm_lat` has been replaced with `lat`, and ', + 'will be removed in version 0.4.1+.']), + DeprecationWarning, stacklevel=2) + return self.lat + @property def lt(self): """Vector local time in hours.""" @@ -656,6 +703,15 @@ def lt(self, lt): self._dat_attr_setter('_lt', lt) return + @property + def aacgm_mlt(self): + """Deprecated magnitude of the vector(s).""" + # TODO(#133): remove after old attributes are deprecated + warnings.warn("".join(['`aacgm_mlt` has been replaced with `lt`, and ', + 'will be removed in version 0.4.1+.']), + DeprecationWarning, stacklevel=2) + return self.lt + @property def height(self): """Vector in km.""" From 6a8eb10bdc2536e4f274df2dfc17d094d077fe5f Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 30 Apr 2024 15:31:18 -0400 Subject: [PATCH 22/60] DOC: updated warning message Updated a warning message to make more sense. --- ocbpy/ocb_scaling.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index b3ccd7f5..574089a5 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -164,11 +164,11 @@ def __init__(self, dat_ind, ocb_ind, lat, lt, height=350.0, else: new_kwargs = [dep_pairs[dep_key] for dep_key in used_dep] warnings.warn("".join(['kwargs have been replaced with new ', - 'names, that reflect their new ', - 'flexibility. Old kwargs will be ', - 'removed in version 0.4.1+. Old kwargs ', - 'used: ', repr(used_dep), '; replace ', - 'with: ', repr(new_kwargs)]), + 'names that reflect their new scope. ', + 'Old kwargs will be removed in version ', + '0.4.1+. Old kwargs used: ', + repr(used_dep), '; replace with: ', + repr(new_kwargs)]), DeprecationWarning, stacklevel=2) if set_mag: From 65702206d1c1691669c4fab5776066bdf12304ea Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 30 Apr 2024 15:32:56 -0400 Subject: [PATCH 23/60] TST: added dep warning tests Added unit tests for the deprecation warnings in the VectorData class. --- ocbpy/tests/test_ocb_scaling.py | 202 ++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index cc0212d0..a1640cfb 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -10,11 +10,213 @@ from numpy import nan # noqa F401 from os import path import unittest +import warnings import ocbpy import ocbpy.tests.class_common as cc +class TestOCBScalingDepWarning(unittest.TestCase): + """Unit tests for Deprecation Warnings in the VectorData class.""" + + def setUp(self): + """Initialize the OCBoundary and VectorData objects.""" + + self.dep_attrs = {'aacgm_lat': 'lat', 'aacgm_mlt': 'lt', + 'aacgm_n': 'vect_n', 'aacgm_e': 'vect_e', + 'aacgm_z': 'vect_z', 'aacgm_mag': 'vect_mag'} + self.vdata_args = [0, 27, 80.0, 5.5] + self.vdata_kwargs = {'vect_n': 1.0, 'vect_e': 0.0, 'vect_z': 0.0, + 'vect_mag': 1.0} + self.warn_msg = '' + return + + def tearDown(self): + """Tear down the test environment.""" + del self.dep_attrs, self.vdata_args, self.vdata_kwargs, self.warn_msg + return + + def test_init_dep_attr(self): + """Test the set up of the VectorData object with a deprecated attr.""" + + # Cycle through the deprecated inputs + for attr in self.dep_attrs.keys(): + # Set the input + kwargs = {key: self.vdata_kwargs[key] + for key in self.vdata_kwargs.keys() + if key != self.dep_attrs[attr]} + + if self.dep_attrs[attr] in self.vdata_kwargs.keys(): + kwargs[attr] = self.vdata_kwargs[self.dep_attrs[attr]] + else: + kwargs[attr] = self.vdata_args[-1] if attr.find( + 'lt') > 0 else self.vdata_args[-2] + + # Set the warning message + self.warn_msg = attr + + with self.subTest(kwargs=kwargs): + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + ocbpy.ocb_scaling.VectorData(*self.vdata_args, **kwargs) + return + + def test_init_dep_attrs(self): + """Test the set up of the VectorData object with deprecated attrs.""" + + # Set the deprecated inputs + for attr in self.dep_attrs: + if self.dep_attrs[attr] in self.vdata_kwargs.keys(): + self.vdata_kwargs[attr] = self.vdata_kwargs[ + self.dep_attrs[attr]] + del self.vdata_kwargs[self.dep_attrs[attr]] + else: + self.vdata_kwargs[attr] = self.vdata_args[-1] if attr.find( + 'lt') > 0 else self.vdata_args[-2] + + # Set the warning message + self.warn_msg = 'Old kwargs will be removed in version 0.4.1+.' + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + ocbpy.ocb_scaling.VectorData(*self.vdata_args, **self.vdata_kwargs) + return + + def test_get_dep_attr(self): + """Test the retrieval of deprecated attributes from VectorData.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + + # Set the warning message + self.warn_msg = "has been replaced" + + # Cycle through the deprecated attributes + for attr in self.dep_attrs.keys(): + with self.subTest(dep_attr=attr): + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + getattr(vdata, attr) + return + + def test_get_dep_attr(self): + """Test the retrieval of deprecated attributes from VectorData.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + + # Set the warning message + self.warn_msg = "has been replaced" + + # Cycle through the deprecated attributes + for attr in self.dep_attrs.keys(): + with self.subTest(dep_attr=attr): + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + getattr(vdata, attr) + return + + def test_set_dep_attr(self): + """Test setting deprecated attributes from VectorData.""" + # Set the warning message + self.warn_msg = "has been replaced" + + # Cycle through the deprecated attributes + for attr in self.dep_attrs.keys(): + # Set the vector data object + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + + # Get the initial value for the object + comp_val = getattr(vdata, self.dep_attrs[attr]) + + with self.subTest(dep_attr=attr): + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + setattr(vdata, attr, comp_val + 1.0) + + self.assertEqual(getattr(vdata, self.dep_attrs[attr]), + comp_val + 1.0, msg="{:} not updated".format( + self.dep_attrs[attr])) + return + + def test_dep_aacgm_mag(self): + """Test VectorData property `aacgm_mag` is deprecated.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + # Set the warning message + self.warn_msg = "has been replaced with `vect_mag`, and will be removed" + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + self.assertEqual(vdata.aacgm_mag, vdata.vect_mag) + return + + def test_dep_aacgm_lat(self): + """Test VectorData property `aacgm_lat` is deprecated.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + # Set the warning message + self.warn_msg = "has been replaced with `lat`, and will be removed" + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + self.assertEqual(vdata.aacgm_lat, vdata.lat) + return + + def test_dep_aacgm_mlt(self): + """Test VectorData property `aacgm_mlt` is deprecated.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + # Set the warning message + self.warn_msg = "has been replaced with `lt`, and will be removed" + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + self.assertEqual(vdata.aacgm_mlt, vdata.lt) + return + + def test_dep_calc_ocb_polar_angle(self): + """Test VectorData method `calc_ocb_polar_angle` is deprecated.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + vdata.ocb_quad = 1 + vdata.vec_quad = 1 + vdata.aacgm_naz = 5.0 + vdata.pole_angle = 3.0 + + # Set the warning message + self.warn_msg = "Instead, use `ocbpy.vectors.calc_dest_polar_angle`" + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + self.assertEqual(vdata.calc_ocb_polar_angle(), 2.0) + return + + def test_dep_calc_ocb_vec_sign(self): + """Test VectorData method `calc_ocb_vec_sign` is deprecated.""" + # Set the vector data + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + vdata.ocb_quad = 1 + vdata.vec_quad = 1 + vdata.aacgm_naz = 5.0 + vdata.pole_angle = 3.0 + + # Set the warning message + self.warn_msg = "Instead, use `ocbpy.vectors.calc_dest_vec_sign`" + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + self.assertDictEqual(vdata.calc_ocb_vec_sign(north=True, east=True), + {'north': 1, 'east': 1}) + return + + class TestOCBScalingLogFailure(cc.TestLogWarnings): """Unit tests for logging messages in ocb_scaling module.""" From 03fa50b0006e785a1d180a2495ee5f0907de657c Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 30 Apr 2024 15:43:39 -0400 Subject: [PATCH 24/60] BUG: removed double test Remove a doubly-defined test and improve the comments. --- ocbpy/tests/test_ocb_scaling.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index a1640cfb..6e758648 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -10,12 +10,12 @@ from numpy import nan # noqa F401 from os import path import unittest -import warnings import ocbpy import ocbpy.tests.class_common as cc +# TODO(#133): delete after deprecated elements are removed class TestOCBScalingDepWarning(unittest.TestCase): """Unit tests for Deprecation Warnings in the VectorData class.""" @@ -99,23 +99,6 @@ def test_get_dep_attr(self): getattr(vdata, attr) return - def test_get_dep_attr(self): - """Test the retrieval of deprecated attributes from VectorData.""" - # Set the vector data - vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, - **self.vdata_kwargs) - - # Set the warning message - self.warn_msg = "has been replaced" - - # Cycle through the deprecated attributes - for attr in self.dep_attrs.keys(): - with self.subTest(dep_attr=attr): - # Capture and evaluate the warning message - with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): - getattr(vdata, attr) - return - def test_set_dep_attr(self): """Test setting deprecated attributes from VectorData.""" # Set the warning message @@ -267,7 +250,7 @@ def test_inconsistent_vector_warning(self): dat_name="Test", dat_units="$m s^{-1}$") - # Test logging error message for each bad initialization + # Test logging error message for the bad initialization self.eval_logging_message() return From 54f999611099245d973fc0c507606c89362e01f2 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 30 Apr 2024 15:44:29 -0400 Subject: [PATCH 25/60] BUG: fix degree range handling Fix the degree handling in `hr2deg` to be consistent with other longitude range handling. --- ocbpy/ocb_time.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/ocbpy/ocb_time.py b/ocbpy/ocb_time.py index 19b2c9ee..d004e74c 100644 --- a/ocbpy/ocb_time.py +++ b/ocbpy/ocb_time.py @@ -8,6 +8,8 @@ import datetime as dt import numpy as np +import ocbpy + def get_datetime_fmt_len(datetime_fmt): """Get the lenght of a string line needed for a specific datetime format. @@ -221,29 +223,44 @@ def deg2hr(lon): return lt -def hr2deg(lt, max_deg=360.0): +def hr2deg(lt, min_deg=-180.0, max_deg=360.0): """Convert from degrees to hours. Parameters ---------- lt : float or array-like Local time-like value in hours + min_deg : float + Minimum number of degrees in desired range, e.g. 0 for 0-360 or + 180 for +/-180 (default=-180.0) max_deg : float Maximum number of degrees in desired range, e.g. 360 for 0-360 or - 180 for +/-180 (default=360.0) + 180 for +/-180. (default=360.0) Returns ------- lon : float or array-like Longitude-like value in degrees + Notes + ----- + If `min_deg` and `max_deg` specify a range less than 360 degrees, `max_deg` + will be used to identify the desired range. Ranges greater than 360 degrees + (e.g., -180 to 360) are allowed. + """ lt = np.asarray(lt) lon = lt * 15.0 # 180 deg/12 hr = 15 deg/hr # Ensure the local time range is realistic - lon = fix_range(lon, max_deg - 360.0, max_deg) + if max_deg - min_deg < 360.0: + min_deg = max_deg - 360.0 + ocbpy.logger.warning( + "adjusting range to be realistic: {:.1f} - {:.1f} deg".format( + min_deg, max_deg)) + + lon = fix_range(lon, min_deg, max_deg, 360) return lon @@ -333,7 +350,7 @@ def slt2glon(slt, dtime): Returns ------- glon : float or array-like - Geographic longitude in degrees + Geographic longitude in degrees, ranging from -180 to 360 """ @@ -363,7 +380,7 @@ def glon2slt(glon, dtime): Returns ------- slt : float or array-like - Solar local time in hours + Solar local time in hours, ranging from 0 up to 24 hours """ From 392c38a64485fd8ebe2a21a4776af1af30eb7717 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 30 Apr 2024 15:45:04 -0400 Subject: [PATCH 26/60] TST: added time logging test Added logging test for the new time logger warning. --- ocbpy/tests/test_ocb_time.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ocbpy/tests/test_ocb_time.py b/ocbpy/tests/test_ocb_time.py index 6d48921f..9fe42308 100644 --- a/ocbpy/tests/test_ocb_time.py +++ b/ocbpy/tests/test_ocb_time.py @@ -10,6 +10,23 @@ import unittest from ocbpy import ocb_time +import ocbpy.tests.class_common as cc + + +class TestOCBTimeLogOutput(cc.TestLogWarnings): + """Unit tests for logging messages in the ocb_time module.""" + + def test_hr2deg_range_warning(self): + """Test the log warning raised for bad input degree range.""" + # Set the expected output + self.lwarn = u"adjusting range to be realistic" + + # Use a range that's too small + self.assertEqual(ocb_time.hr2deg(-24.0, min_deg=180, max_deg=360), 0.0) + + # Test logging error message for the bad range + self.eval_logging_message() + return class TestOCBTimeMethods(unittest.TestCase): From 62d78b9fd1089a509999ec24d6ceb9e086845479 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 09:10:34 -0400 Subject: [PATCH 27/60] BUG: added missing attribute warning Added an attribute deprecation warning for `aacgm_naz`. --- ocbpy/ocb_scaling.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 574089a5..89557a5c 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -352,7 +352,7 @@ def __setattr__(self, name, value): return # TODO(#133): remove after old attributes are deprecated - def __getattr__(self, name, **kwargs): + def __getattribute__(self, name, **kwargs): """Get attributes, allowing access to deprecated names. Parameters @@ -375,6 +375,9 @@ def __getattr__(self, name, **kwargs): 'removed in version 0.4.1+.']), DeprecationWarning, stacklevel=2) name = dep_pairs[name] + elif name == "aacgm_naz": + warnings.warn('`aacgm_naz` will be removed in version 0.4.1+.', + DeprecationWarning, stacklevel=2) # Use Object to avoid recursion value = super(VectorData, self).__getattribute__(name) From b2165d5c4841120f61384a49284ac4bdc1f3d708 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 09:11:05 -0400 Subject: [PATCH 28/60] TST: added `aacgm_naz` dep test Added a test for the `aacgm_naz` deprecation warning. --- ocbpy/tests/test_ocb_scaling.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index 6e758648..65959062 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -123,6 +123,21 @@ def test_set_dep_attr(self): self.dep_attrs[attr])) return + def test_get_aacgm_naz(self): + """Test retrieval of deprecated `aacgm_naz` from VectorData.""" + # Set the warning message + self.warn_msg = "will be removed in version 0.4.1+." + + # Set the vector data object + vdata = ocbpy.ocb_scaling.VectorData(*self.vdata_args, + **self.vdata_kwargs) + + # Capture and evaluate the warning message + with self.assertWarnsRegex(DeprecationWarning, self.warn_msg): + self.assertTrue(numpy.isnan(vdata.aacgm_naz)) + + return + def test_dep_aacgm_mag(self): """Test VectorData property `aacgm_mag` is deprecated.""" # Set the vector data From d436c05592ffa01ff552de0282101bd3a2b0933b Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 09:46:32 -0400 Subject: [PATCH 29/60] BUG: fixed scaling attributes Scale the OCB vector components, not the original vector components. --- ocbpy/ocb_scaling.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 89557a5c..d67b0c61 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -936,11 +936,11 @@ def scale_vector(self): shape=self.ocb_z.shape, fill_value=self.scale_func( self.vect_z, self.unscaled_r, self.scaled_r)) else: - self.ocb_n = self.scale_func(self.vect_n, self.unscaled_r, + self.ocb_n = self.scale_func(self.ocb_n, self.unscaled_r, self.scaled_r) - self.ocb_e = self.scale_func(self.vect_e, self.unscaled_r, + self.ocb_e = self.scale_func(self.ocb_e, self.unscaled_r, self.scaled_r) - self.ocb_z = self.scale_func(self.vect_z, self.unscaled_r, + self.ocb_z = self.scale_func(self.ocb_z, self.unscaled_r, self.scaled_r) # Calculate the scaled OCB vector magnitude From 09da329fef6d5e763917ef283cdcb6aa454146ef Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 09:47:33 -0400 Subject: [PATCH 30/60] DEP: update VectorData in SuperMAG Update the VectorData call in SuperMAG to reduce deprecation warnings. --- ocbpy/instruments/supermag.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ocbpy/instruments/supermag.py b/ocbpy/instruments/supermag.py index 3699cd7e..9f521487 100644 --- a/ocbpy/instruments/supermag.py +++ b/ocbpy/instruments/supermag.py @@ -157,8 +157,8 @@ def supermag2ascii_ocb(smagfile, outfile, hemisphere=0, ocb=None, # Set this value's AACGM vector values vdata = ocbscal.VectorData( itime[0], ocb.rec_ind, mdata['MLAT'][itime], - mdata['MLT'][itime], aacgm_n=mdata['BN'][itime], - aacgm_e=mdata['BE'][itime], aacgm_z=mdata['BZ'][itime], + mdata['MLT'][itime], vect_n=mdata['BN'][itime], + vect_e=mdata['BE'][itime], vect_z=mdata['BZ'][itime], scale_func=scale_func) vdata.set_ocb(ocb) @@ -183,11 +183,11 @@ def supermag2ascii_ocb(smagfile, outfile, hemisphere=0, ocb=None, mdata[okey][jmag]) outline = "".join([ - outline, "{:.2f} ".format(vdata.aacgm_lat[tind]), + outline, "{:.2f} ".format(vdata.lat[tind]), "{:.2f} {:.2f} {:.2f} {:.2f} {:.2f} {:.2f} ".format( - vdata.aacgm_mlt[tind], vdata.aacgm_mag[tind], - vdata.aacgm_n[tind], vdata.aacgm_e[tind], - vdata.aacgm_z[tind], vdata.ocb_lat[tind]), + vdata.lt[tind], vdata.vect_mag[tind], + vdata.vect_n[tind], vdata.vect_e[tind], + vdata.vect_z[tind], vdata.ocb_lat[tind]), "{:.2f} {:.2f} {:.2f} {:.2f} {:.2f}\n".format( vdata.ocb_mlt[tind], vdata.ocb_mag[tind], vdata.ocb_n[tind], vdata.ocb_e[tind], From 0fb19edc46addfdf365be95de3f2f89b7ce8b8b1 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 10:15:05 -0400 Subject: [PATCH 31/60] STY: fixed indentation Fix over indented lines. --- ocbpy/ocb_scaling.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index d67b0c61..3356227a 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -576,7 +576,7 @@ def aacgm_mag(self): # TODO(#133): remove after old attributes are deprecated warnings.warn("".join(['`aacgm_mag` has been replaced with `vect_mag`,', ' and will be removed in version 0.4.1+.']), - DeprecationWarning, stacklevel=2) + DeprecationWarning, stacklevel=2) return self.vect_mag @property @@ -692,7 +692,7 @@ def aacgm_lat(self): # TODO(#133): remove after old attributes are deprecated warnings.warn("".join(['`aacgm_lat` has been replaced with `lat`, and ', 'will be removed in version 0.4.1+.']), - DeprecationWarning, stacklevel=2) + DeprecationWarning, stacklevel=2) return self.lat @property @@ -712,7 +712,7 @@ def aacgm_mlt(self): # TODO(#133): remove after old attributes are deprecated warnings.warn("".join(['`aacgm_mlt` has been replaced with `lt`, and ', 'will be removed in version 0.4.1+.']), - DeprecationWarning, stacklevel=2) + DeprecationWarning, stacklevel=2) return self.lt @property From 8225e88c11665fc63e62e588f19de4cda95f25e4 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 10:15:35 -0400 Subject: [PATCH 32/60] BUG: fixed imports Allow code to run with either ssj_auroral_boundaries or zenodo_get. --- ocbpy/boundaries/dmsp_ssj_files.py | 49 ++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/ocbpy/boundaries/dmsp_ssj_files.py b/ocbpy/boundaries/dmsp_ssj_files.py index c823de87..f2dd63c9 100644 --- a/ocbpy/boundaries/dmsp_ssj_files.py +++ b/ocbpy/boundaries/dmsp_ssj_files.py @@ -25,18 +25,30 @@ import warnings import zipfile +import aacgmv2 + import ocbpy -err = ''.join(['unable to load the DMSP SSJ module; ssj_auroral_boundary ', - 'is available at: ', - 'https://github.com/lkilcommons/ssj_auroral_boundary']) try: from spacepy import pycdf - import aacgmv2 import ssj_auroral_boundary as ssj + err = '' +except ImportError as ierr: + ssj = None + err = ''.join(['unable to load the DMSP SSJ module; ssj_auroral_boundary ', + 'is available at: ', + 'https://github.com/lkilcommons/ssj_auroral_boundary\n', + str(ierr)]) + +try: import zenodo_get except ImportError as ierr: - raise ImportError("{:s}\n{:}".format(err, ierr)) + zenodo_get = None + err = ''.join([err, '\nunable to load `zenodo_get` module; avalable ', + 'from PyPi.\n', str(ierr)]) + +if ssj is None and zenodo_get is None: + raise ImportError(err) def fetch_ssj_files(stime, etime, out_dir=None, sat_nums=None): @@ -77,6 +89,11 @@ def fetch_ssj_files(stime, etime, out_dir=None, sat_nums=None): ssj_auroral_boundaries package is no longer supported; use `fetch_ssj_boundary_files` + Raises + ------ + ImportError + If called and ssj_auroral_boundaries is not available + See Also --------- requests.exceptions.ProxyError @@ -88,6 +105,10 @@ def fetch_ssj_files(stime, etime, out_dir=None, sat_nums=None): " will be removed in version 0.4.1+."]), DeprecationWarning, stacklevel=2) + if ssj is None: + raise ImportError( + 'depends on uninstalled package ssj_auroral_boundaries') + # Get and test the output directory if out_dir is None: out_dir = ocbpy.boundaries.files.get_boundary_directory() @@ -135,8 +156,8 @@ def fetch_ssj_files(stime, etime, out_dir=None, sat_nums=None): try: ssj.files.download_cdf_from_noaa(remote, local) out_files.append(local) - except RuntimeError as err: - ocbpy.logger.info(err) + except RuntimeError as rerr: + ocbpy.logger.info(rerr) # Cycle by one day ctime += dt.timedelta(days=1) @@ -175,6 +196,8 @@ def create_ssj_boundary_files(cdf_files, out_dir=None, ------ ValueError If incorrect input is provided + ImportError + If called and ssj_auroral_boundaries is not available Warnings -------- @@ -189,6 +212,10 @@ def create_ssj_boundary_files(cdf_files, out_dir=None, " will be removed in version 0.4.1+."]), DeprecationWarning, stacklevel=2) + if ssj is None: + raise ImportError( + 'depends on uninstalled package ssj_auroral_boundaries') + # Test the directory inputs if out_dir is None: out_dir = ocbpy.boundaries.files.get_boundary_directory() @@ -225,8 +252,8 @@ def create_ssj_boundary_files(cdf_files, out_dir=None, make_plot=make_plots, csvvars=out_cols) out_files.append(absd.csv.csvfn) - except pycdf.CDFError as err: - ocbpy.logger.warning("{:}".format(err)) + except pycdf.CDFError as cerr: + ocbpy.logger.warning("{:}".format(cerr)) except Warning as war: ocbpy.logger.warning("{:}".format(war)) else: @@ -268,6 +295,8 @@ def fetch_ssj_boundary_files(stime=None, etime=None, out_dir=None, If an unknown satellite ID is provided. IOError If unable to donwload the target archive and identify the zip file + ImportError + If called and zenodo_get is not available Notes ----- @@ -275,6 +304,8 @@ def fetch_ssj_boundary_files(stime=None, etime=None, out_dir=None, without downloading it again. """ + if zenodo_get is None: + raise ImportError('depends on uninstalled package zenodo_get') # Test the requested satellite outputs. SSJ5 was carried on F16 onwards. # F19 was short lived, F20 was not launched. Ref: From c86079e77219f1dba64948dddbe4b2bffd04745a Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 13:58:31 -0400 Subject: [PATCH 33/60] MAINT: filter deprecation warnings Filter deprecation warnings to avoid alerting user when there isn't anything they can change. --- ocbpy/ocb_scaling.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 3356227a..3a5f949d 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -728,6 +728,7 @@ def height(self, height): def clear_data(self): """Clear or initialize the output data attributes.""" + warnings.simplefilter("ignore") # Assign the OCB vector default values and location self.ocb_n = np.full(shape=self.vshape, fill_value=np.nan) @@ -743,6 +744,8 @@ def clear_data(self): self.aacgm_naz = np.full(shape=self.vshape, fill_value=np.nan) self.ocb_aacgm_lat = np.full(shape=self.vshape, fill_value=np.nan) self.ocb_aacgm_mlt = np.full(shape=self.vshape, fill_value=np.nan) + + warnings.resetwarnings() return def set_ocb(self, ocb, scale_func=None): @@ -896,6 +899,8 @@ def scale_vector(self): be removed in version 0.4.1+. """ + warnings.simplefilter("ignore") + # Test input if np.all(np.isnan(self.lat)) or np.all(np.isnan(self.lt)): raise ValueError("Vector locations required") @@ -946,6 +951,7 @@ def scale_vector(self): # Calculate the scaled OCB vector magnitude self.ocb_mag = np.sqrt(self.ocb_n**2 + self.ocb_e**2 + self.ocb_z**2) + warnings.resetwarnings() return def calc_ocb_polar_angle(self): @@ -973,6 +979,7 @@ def calc_ocb_polar_angle(self): DeprecationWarning, stacklevel=2) # Test input + warnings.simplefilter("ignore") if np.all(np.isnan(self.aacgm_naz)): raise ValueError("AACGM North polar angle undefined") @@ -983,6 +990,7 @@ def calc_ocb_polar_angle(self): ocb_naz = vectors.calc_dest_polar_angle( self.ocb_quad, self.vec_quad, self.aacgm_naz, self.pole_angle) + warnings.resetwarnings() return ocb_naz def calc_ocb_vec_sign(self, north=False, east=False, quads=None): @@ -1022,6 +1030,7 @@ def calc_ocb_vec_sign(self, north=False, east=False, quads=None): DeprecationWarning, stacklevel=2) # Test input + warnings.simplefilter("ignore") if not np.any([north, east]): raise ValueError("must set at least one direction") @@ -1036,6 +1045,7 @@ def calc_ocb_vec_sign(self, north=False, east=False, quads=None): self.ocb_quad, self.vec_quad, self.aacgm_naz, self.pole_angle, north=north, east=east, quads=quads) + warnings.resetwarnings() return vsigns def calc_vec_pole_angle(self): From 3d6ecff432d0a8fbd3fd32fed7e59637d66bb990 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 13:59:06 -0400 Subject: [PATCH 34/60] TST: reduce deprecation warnings Update OCB scaling tests to reduce deprecation warnings. --- ocbpy/tests/test_ocb_scaling.py | 78 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index 65959062..c796d945 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -230,8 +230,8 @@ def setUp(self): self.ocb = ocbpy.OCBoundary(filename=test_file, instrument='image') self.ocb.rec_ind = 27 self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, 75.0, - 22.0, aacgm_n=50.0, - aacgm_e=86.5, aacgm_z=5.0, + 22.0, vect_n=50.0, + vect_e=86.5, vect_z=5.0, dat_name="Test", dat_units="$m s^{-1}$") return @@ -261,7 +261,7 @@ def test_inconsistent_vector_warning(self): # Initalize the VectorData class with inconsistent vector magnitudes self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, 75.0, 22.0, - aacgm_mag=100.0, + vect_mag=100.0, dat_name="Test", dat_units="$m s^{-1}$") @@ -284,19 +284,19 @@ def setUp(self): 'ocb_z'] self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, 75.0, - 22.0, aacgm_n=50.0, - aacgm_e=86.5, aacgm_z=5.0, + 22.0, vect_n=50.0, + vect_e=86.5, vect_z=5.0, dat_name="Test", dat_units="$m s^{-1}$") self.wdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, 75.0, - 22.0, aacgm_n=50.0, - aacgm_e=86.5, aacgm_z=5.0, - aacgm_mag=100.036243432, + 22.0, vect_n=50.0, + vect_e=86.5, vect_z=5.0, + vect_mag=100.036243432, dat_name="Test", dat_units="$m s^{-1}$") self.zdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, 87.2, - 21.22, aacgm_n=0.0, - aacgm_e=0.0, + 21.22, vect_n=0.0, + vect_e=0.0, dat_name="Test Zero", dat_units="$m s^{-1}$") return @@ -700,7 +700,7 @@ def test_scale_vec(self): return def test_scale_vec_z_zero(self): - """Test the calc of the OCB vector sign with no vertical aacgm_z.""" + """Test the calc of the OCB vector sign with no vertical vect_z.""" # Re-assing the necessary variable self.vdata.vect_z = 0.0 @@ -811,19 +811,19 @@ def setUp(self): 'ocb_z'] self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, - 75.0, 22.0, aacgm_n=50.0, - aacgm_e=86.5, aacgm_z=5.0, + 75.0, 22.0, vect_n=50.0, + vect_e=86.5, vect_z=5.0, dat_name="Test", dat_units="$m s^{-1}$") self.wdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, - 75.0, 22.0, aacgm_n=50.0, - aacgm_e=86.5, aacgm_z=5.0, - aacgm_mag=100.036243432, + 75.0, 22.0, vect_n=50.0, + vect_e=86.5, vect_z=5.0, + vect_mag=100.036243432, dat_name="Test", dat_units="$m s^{-1}$") self.zdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, - 87.2, 21.22, aacgm_n=0.0, - aacgm_e=0.0, + 87.2, 21.22, vect_n=0.0, + vect_e=0.0, dat_name="Test Zero", dat_units="$m s^{-1}$") return @@ -938,8 +938,8 @@ def setUp(self): self.ocb = ocbpy.OCBoundary(filename=test_file, instrument='image') self.ocb.rec_ind = 27 self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, 75.0, - 22.0, aacgm_n=50.0, - aacgm_e=86.5, aacgm_z=5.0, + 22.0, vect_n=50.0, + vect_e=86.5, vect_z=5.0, dat_name="Test", dat_units="$m s^{-1}$") self.input_attrs = list() @@ -957,8 +957,8 @@ def tearDown(self): def test_init_ocb_array_failure(self): """Test init failure with mismatched OCB and input array input.""" self.input_attrs = [0, [27, 31], 75.0, 22.0] - self.bad_input = {'aacgm_n': 100.0, 'aacgm_e': 100.0, - 'aacgm_z': 10.0, 'ocb_lat': 81.0, + self.bad_input = {'vect_n': 100.0, 'vect_e': 100.0, + 'vect_z': 10.0, 'ocb_lat': 81.0, 'ocb_mlt': [2.0, 5.8, 22.5]} with self.assertRaisesRegex(ValueError, "OCB index and input shapes"): @@ -970,9 +970,9 @@ def test_init_ocb_vector_failure(self): """Test init failure with mismatched OCB and data array input.""" self.input_attrs = [[3, 6, 0], [27, 31], [75.0, 87.2, 65.0], [22.0, 21, 22]] - self.bad_input = {'aacgm_n': [100.0, 110.0, 30.0], - 'aacgm_e': [100.0, 110.0, 30.0], - 'aacgm_z': [10.0, 10.0, 3.0]} + self.bad_input = {'vect_n': [100.0, 110.0, 30.0], + 'vect_e': [100.0, 110.0, 30.0], + 'vect_z': [10.0, 10.0, 3.0]} with self.assertRaisesRegex(ValueError, "Mismatched OCB and Vector input shapes"): @@ -995,9 +995,9 @@ def test_init_vector_failure(self): [[0, 1], self.ocb.rec_ind, [75.0, 70.0], 22.0], [[0, 1], self.ocb.rec_ind, [75.0, 70.0], [22.0, 20.0, 23.0]]] - self.bad_input = [{'aacgm_n': 10.0}, - {'aacgm_n': [100.0, 110.0, 30.0]}, - {'aacgm_n': [100.0, 110.0, 30.0]}] + self.bad_input = [{'vect_n': 10.0}, + {'vect_n': [100.0, 110.0, 30.0]}, + {'vect_n': [100.0, 110.0, 30.0]}] self.raise_out = ['data index shape must match vector shape', 'mismatched dimensions for VectorData inputs', 'mismatched dimensions for VectorData inputs'] @@ -1303,15 +1303,15 @@ def setUp(self): self.vdata = None self.out = None - self.aacgm_mag = numpy.full(shape=(17,), fill_value=10.44030650891055) - self.aacgm_mag[0] = 11.575836902790225 - self.aacgm_mag[-1] = 0.0 + self.vect_mag = numpy.full(shape=(17,), fill_value=10.44030650891055) + self.vect_mag[0] = 11.575836902790225 + self.vect_mag[-1] = 0.0 return def tearDown(self): """Tear down the test environment.""" del self.ocb, self.vargs, self.vkwargs, self.out, self.vdata - del self.aacgm_mag, self.ref_quads + del self.vect_mag, self.ref_quads return def set_vector_ocb_ind(self): @@ -1369,7 +1369,7 @@ def test_init_nez_vec_array(self): self.assertEqual(len(self.vdata.vect_mag), len(self.vargs[0])) self.assertEqual(len(self.vdata.ocb_mag), len(self.vargs[0])) for i, self.out in enumerate(self.vdata.vect_mag): - self.assertAlmostEqual(self.out, self.aacgm_mag[i]) + self.assertAlmostEqual(self.out, self.vect_mag[i]) return def test_init_nez_ocb_vec_array(self): @@ -1379,7 +1379,7 @@ def test_init_nez_ocb_vec_array(self): self.assertEqual(len(self.vdata.vect_mag), len(self.vargs[0])) self.assertEqual(len(self.vdata.ocb_mag), len(self.vargs[0])) for i, self.out in enumerate(self.vdata.vect_mag): - self.assertAlmostEqual(self.out, self.aacgm_mag[i]) + self.assertAlmostEqual(self.out, self.vect_mag[i]) return def test_init_nez_ocb_array(self): @@ -1393,17 +1393,17 @@ def test_init_nez_ocb_array(self): self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.assertEqual(len(self.vdata.ocb_mag), len(self.vargs[1])) - self.assertTrue(abs(self.vdata.vect_mag - self.aacgm_mag[0]).all() + self.assertTrue(abs(self.vdata.vect_mag - self.vect_mag[0]).all() < 1.0e-7, msg="unexpected AACGM vector magnitude") return def test_init_mag(self): """Test the set up of the VectorData array input with magnitude.""" - self.vkwargs['aacgm_mag'] = self.aacgm_mag + self.vkwargs['vect_mag'] = self.vect_mag self.vdata = ocbpy.ocb_scaling.VectorData(*self.vargs, **self.vkwargs) self.assertEqual(len(self.vdata.vect_mag), len(self.vargs[0])) for i, self.out in enumerate(self.vdata.vect_mag): - self.assertAlmostEqual(self.out, self.aacgm_mag[i]) + self.assertAlmostEqual(self.out, self.vect_mag[i]) return def test_vector_all_bad_lat(self): @@ -1458,8 +1458,8 @@ def test_vector_some_bad_lat(self): self.assertRegex(self.out, self.vkwargs[vkey]) # Ensure the right hemisphere is good - self.assertAlmostEqual(self.vdata.vect_mag[1], self.aacgm_mag[1]) - self.assertAlmostEqual(self.vdata.ocb_mag[1], self.aacgm_mag[1]) + self.assertAlmostEqual(self.vdata.vect_mag[1], self.vect_mag[1]) + self.assertAlmostEqual(self.vdata.ocb_mag[1], self.vect_mag[1]) return def test_calc_vec_pole_angle_flat(self): From 4efd53b1d9daecd8c473a4e2e65aa6190188fb07 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 15:09:02 -0400 Subject: [PATCH 35/60] ENH: add geo option to pysat Add the option to use geographic coordinates to the pysat Instrument OCB function. --- ocbpy/instruments/pysat_instruments.py | 133 +++++++++++++++---------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/ocbpy/instruments/pysat_instruments.py b/ocbpy/instruments/pysat_instruments.py index 950414f4..1d5d237a 100644 --- a/ocbpy/instruments/pysat_instruments.py +++ b/ocbpy/instruments/pysat_instruments.py @@ -24,10 +24,11 @@ import ocbpy.ocb_scaling as ocbscal -def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, - curl_evar_names=None, vector_names=None, hemisphere=0, - ocb=None, ocbfile='default', instrument='', max_sdiff=60, - min_merit=None, max_merit=None, **kwargs): +def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', + evar_names=None, curl_evar_names=None, vector_names=None, + height=350.0, hemisphere=0, ocb=None, ocbfile='default', + instrument='', max_sdiff=60, min_merit=None, max_merit=None, + loc_coord='magnetic', vect_coord='magnetic', **kwargs): """Covert the location of pysat data into OCB, EAB, or Dual coordinates. Parameters @@ -35,9 +36,11 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, pysat_inst : pysat.Instrument pysat.Instrument class object containing magnetic coordinates mlat_name : str - Instrument data key or column for magnetic latitudes (default='') + Instrument data key or column for latitudes (default='') mlt_name : str - Instrument data key or column for magnetic local times (default='') + Instrument data key or column for local times (default='') + height_name : str + Instrument data key or column for altitude (default='') evar_names : list or NoneType List of Instrument data keys or columns pointing to measurements that are proportional to the electric field (E); e.g. ion drift @@ -53,6 +56,9 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, provided. The value corresponding to each key must be a dict that indicates the names holding data needed to initialise the ocbpy.ocb_scaling.VectorData object (default=None) + height : float or array-like + Altitude value(s) to use if no height variable is provided by + `height_name` (default=350.0) hemisphere : int Hemisphere to process (can only do one at a time). 1=Northern, -1=Southern, 0=Determine from data (default=0) @@ -74,6 +80,16 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, max_merit : float or NoneTye Maximum value for the default figure of merit or None to not apply a custom maximum (default=None) + loc_coord : str + Name of the coordinate system for `mlat_name` and `mlt_name`; one of + 'magnetic', 'geocentric', or 'geodetic'. If not 'magnetic', + `height_name` or `height` will be used to convert the data to magnetic + coordinates. (default='magnetic') + vect_coord : str + Name of the coordinate system for `vect_n` and `vect_e`; one of + 'magnetic', 'geocentric', or 'geodetic'. If not 'magnetic', + `height_name` or `height` will be used to convert the data to magnetic + coordinates. (default='magnetic') kwargs : dict Dict with optional selection criteria. The key should correspond to a data attribute and the value must be a tuple with the first value @@ -96,9 +112,9 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, :: # Example vector name input looks like: - vector_names={'vel': {'aacgm_n': 'vel_n', 'aacgm_e': 'vel_e', + vector_names={'vel': {'vect_n': 'vel_n', 'vect_e': 'vel_e', 'dat_name': 'velocity', 'dat_units': 'm/s'}, - 'dat': {'aacgm_n': 'dat_n', 'aacgm_e': 'dat_e', + 'dat': {'vect_n': 'dat_n', 'vect_e': 'dat_e', 'scale_func': local_scale_func}} """ @@ -162,7 +178,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, nvect = len(vector_names.keys()) vector_attrs = dict() if nvect > 0: - vector_reqs = ["aacgm_n", "aacgm_e", "aacgm_z"] + vector_reqs = ["vect_n", "vect_e", "vect_z"] for eattr in vector_names.keys(): vdim = 0 @@ -210,10 +226,16 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, for eattr in curl_evar_names: ocb_names.append("{:s}_ocb".format(eattr)) - # Extract the magnetic locations as numpy arrays - aacgm_lat = np.array(pysat_inst[mlat_name]) - aacgm_mlt = np.array(pysat_inst[mlt_name]) - ndat = len(aacgm_lat) + # Extract the locations as numpy arrays + lat = np.array(pysat_inst[mlat_name]) + lt = np.array(pysat_inst[mlt_name]) + ndat = len(lat) + + # Extract the height, if possible + if height_name in pysat_inst.variables: + height = np.array(pysat_inst[height_name]) + else: + height = np.asarray(height) # Load the OCB data for the data period, if desired if ocb is None or (not isinstance(ocb, ocbpy.OCBoundary) @@ -223,12 +245,12 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, # If hemisphere isn't specified, set it here if hemisphere == 0: - hemisphere = np.sign(np.nanmax(aacgm_lat)) + hemisphere = np.sign(np.nanmax(lat)) # Ensure that all data is in the same hemisphere if hemisphere == 0: - hemisphere = np.sign(np.nanmin(aacgm_lat)) - elif hemisphere != np.sign(np.nanmin(aacgm_lat)): + hemisphere = np.sign(np.nanmin(lat)) + elif hemisphere != np.sign(np.nanmin(lat)): raise ValueError("".join(["cannot process observations from " "both hemispheres at the same time;" "set hemisphere=+/-1 to choose."])) @@ -243,7 +265,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, # Ensure all data is from one hemisphere and is finite if pysat_inst.pandas_format: - finite_mask = ((np.sign(aacgm_lat) == hemisphere) + finite_mask = ((np.sign(lat) == hemisphere) & np.isfinite(pysat_inst[:, pysat_names].max(axis=1))) dat_coords = [pysat_inst.index.name] combo_shape = [pysat_inst.index.shape[0]] @@ -260,40 +282,47 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, ocbpy.logger.error("no data in Boundary file(s)") return - # Ensure the MLT and MLat data are the same shape - if(aacgm_lat.shape != aacgm_mlt.shape - or aacgm_lat.shape[0] != pysat_inst.index.shape[0]): - ocb_coords = [mlt_coord for mlt_coord + # Ensure the LT, Lat, and Height data are the same shape + if lat.shape != lt.shape or lat.shape[0] != pysat_inst.index.shape[ + 0] or (height.shape != lat.shape and len(height.shape) > 0): + ocb_coords = [lt_coord for lt_coord in pysat_inst[mlt_name].coords.keys()] if pysat_inst.index.name in ocb_coords: - combo_shape = list(aacgm_mlt.shape) + combo_shape = list(lt.shape) else: - # Ensure MLT has time dependence + # Ensure Local Time has universal time dependence ocb_coords.insert(0, pysat_inst.index.name) combo_shape = [pysat_inst.index.shape[0]] - combo_shape.extend(list(aacgm_mlt.shape)) - out_mlt, _ = np.meshgrid(aacgm_mlt, pysat_inst.index) + combo_shape.extend(list(lt.shape)) + out_lt, _ = np.meshgrid(lt, pysat_inst.index) - if out_mlt.shape != combo_shape: - aacgm_mlt = out_mlt.reshape(combo_shape) + if out_lt.shape != combo_shape: + lt = out_lt.reshape(combo_shape) - # Expand the coordinates if the MLat coordinates are not the - # same as the MLT coordinates + # Expand the coordinates if the lat coordinates are not the + # same as the LT coordinates or height coordinatess for lat_coord in pysat_inst[mlat_name].coords: if lat_coord not in pysat_inst[mlt_name].coords: combo_shape.append(pysat_inst[lat_coord].shape[0]) ocb_coords.append(lat_coord) + if height_name in pysat_inst.variables: + for alt_coord in pysat_inst[height_name].coords: + if alt_coord not in pysat_inst[mlt_name].coords: + combo_shape.append(pysat_inst[alt_coord].shape[0]) + ocb_coords.append(alt_coord) + # Reshape the data - out_lat, out_mlt = np.meshgrid(aacgm_lat, aacgm_mlt) - aacgm_lat = out_lat.reshape(combo_shape) - aacgm_mlt = out_mlt.reshape(combo_shape) + out_lat, out_lt, out_height = np.meshgrid(lat, lt, height) + lat = out_lat.reshape(combo_shape) + lt = out_lt.reshape(combo_shape) + height = out_height.reshape(combo_shape) else: ocb_coords = [pysat_inst.index.name] # See if the data has more dimensions than the OCB coordinates if len(dat_coords) > len(ocb_coords): - combo_shape = list(aacgm_lat.shape) + combo_shape = list(lat.shape) for dcoord in dat_coords: if dcoord not in ocb_coords: ocb_coords.append(dcoord) @@ -301,10 +330,12 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, # Reverse and transpose the arrays combo_shape.reverse() - out_lat = np.full(shape=combo_shape, fill_value=aacgm_lat.transpose()) - out_mlt = np.full(shape=combo_shape, fill_value=aacgm_mlt.transpose()) - aacgm_lat = out_lat.transpose() - aacgm_mlt = out_mlt.transpose() + out_lat = np.full(shape=combo_shape, fill_value=lat.transpose()) + out_lt = np.full(shape=combo_shape, fill_value=lt.transpose()) + out_height = np.full(shape=combo_shape, fill_value=height.transpose()) + lat = out_lat.transpose() + lt = out_lt.transpose() + height = out_height.transpose() # Initialise the OCB output ocb_output = dict() @@ -314,10 +345,9 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, for vattr in ocb_vect_attrs: ovattr = '_'.join([oattr, vattr]) ovattr = ovattr.replace('ocb_ocb_', 'ocb_') - ocb_output[ovattr] = np.full(aacgm_lat.shape, np.nan, - dtype=float) + ocb_output[ovattr] = np.full(lat.shape, np.nan, dtype=float) else: - ocb_output[oattr] = np.full(aacgm_lat.shape, np.nan, dtype=float) + ocb_output[oattr] = np.full(lat.shape, np.nan, dtype=float) # Cycle through the data, matching data and OCB records idat = 0 @@ -356,7 +386,8 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, time_mask = None # Get the OCB coordinates - nout = ocb.normal_coord(aacgm_lat[iout], aacgm_mlt[iout]) + nout = ocb.normal_coord(lat[iout], lt[iout], coords=loc_coord, + height=height[iout]) if len(nout) == 3: ocb_output[olat_name][iout] = nout[0] @@ -367,16 +398,18 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, ocb_output[omlt_name][iout] = nout[1] ocb_output[ocor_name][iout] = nout[3] - # Scale and orient the vector values + # Scale and orient the vector data if nvect > 0: - # Set this value's AACGM vector values + # Set this value's vector data vector_default = {"ocb_lat": ocb_output[olat_name][iout], "ocb_mlt": ocb_output[omlt_name][iout], "r_corr": ocb_output[ocor_name][iout], - "aacgm_n": 0.0, "aacgm_e": 0.0, - "aacgm_z": 0.0, "aacgm_mag": np.nan, - "dat_name": None, "dat_units": None, - "scale_func": None} + "vect_n": 0.0, "vect_e": 0.0, "vect_z": 0.0, + "vect_mag": np.nan, "dat_name": None, + "dat_units": None, "scale_func": None, + "loc_coord": loc_coord, + "vect_coord": vect_coord, + "height": height[iout]} vector_init = dict(vector_default) vshape = list() @@ -404,10 +437,8 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', evar_names=None, 'vector variables must all have the same shape') # Perform the vector scaling - vout = ocbscal.VectorData(vind, ocb.rec_ind, - aacgm_lat[iout], - aacgm_mlt[iout], - **vector_init) + vout = ocbscal.VectorData(vind, ocb.rec_ind, lat[iout], + lt[iout], **vector_init) vout.set_ocb(ocb) # Assign the vector attributes to the output From cb25b2162c1dc6a98c7d3b94a5b0035e22fcdc2f Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 1 May 2024 15:09:31 -0400 Subject: [PATCH 36/60] TST: update pysat tests Update the pysat tests for the new functionality. --- ocbpy/tests/test_pysat.py | 95 ++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/ocbpy/tests/test_pysat.py b/ocbpy/tests/test_pysat.py index d8e66ec9..4b65166e 100644 --- a/ocbpy/tests/test_pysat.py +++ b/ocbpy/tests/test_pysat.py @@ -46,6 +46,7 @@ def setUp(self): self.ocb_key = 'ocb_test' self.pysat_key = 'dummy1' self.pysat_lat = 'latitude' + self.pysat_alt = 'altitude' self.notes = None self.test_inst = None self.ocb = None @@ -58,7 +59,7 @@ def tearDown(self): """Tear down the testing environment.""" del self.ocb_key, self.pysat_key, self.notes, self.del_time del self.test_inst, self.ocb, self.added_keys, self.pysat_keys - del self.pysat_lat + del self.pysat_lat, self.pysat_alt return def eval_ocb_metadata(self): @@ -212,15 +213,20 @@ def test_add_ocb_to_data_defaults(self): """Test the add_ocb_to_data function defaults.""" defaults = ocb_pysat.add_ocb_to_data.__defaults__ - for i in [0, 1, 8]: - self.assertEqual(len(defaults[i]), 0) + for i in [0, 1, 2, 10]: + self.assertEqual(len(defaults[i]), 0, + msg="Default {:d} value {:} != 0".format( + i, defaults[i])) - for i in [2, 3, 4, 6, 10, 11]: + for i in [3, 4, 5, 8, 12, 13]: self.assertIsNone(defaults[i]) - self.assertEqual(defaults[5], 0) - self.assertRegex(defaults[7], 'default') - self.assertEqual(defaults[9], 60) + self.assertEqual(defaults[6], 350) + self.assertEqual(defaults[7], 0) + self.assertRegex(defaults[9], 'default') + self.assertEqual(defaults[11], 60) + self.assertRegex(defaults[14], 'magnetic') + self.assertRegex(defaults[15], 'magnetic') return def test_add_ocb_to_metadata_defaults(self): @@ -365,7 +371,8 @@ def test_add_ocb_to_data_ocb_obj(self): # Test adding OCBs ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", - ocb=self.ocb, max_sdiff=self.del_time) + height_name=self.pysat_alt, ocb=self.ocb, + max_sdiff=self.del_time) self.added_keys = [kk for kk in self.test_inst.meta.keys() if kk.find('_ocb') > 0] @@ -389,6 +396,7 @@ def test_add_ocb_to_data_ocb_file(self): # Test adding OCBs using filename instead of OCB object self.ocb_kw['ocbfile'] = self.ocb_kw['filename'] ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, max_sdiff=self.del_time, **self.ocb_kw) self.added_keys = [kk for kk in self.test_inst.meta.keys() @@ -425,6 +433,7 @@ def test_add_ocb_to_data_southern_hemisphere(self): # Add the OCB data to the Instrument and evaluate the output ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, max_sdiff=self.del_time, **self.ocb_kw) self.test_ocb_added() return @@ -436,6 +445,7 @@ def test_add_ocb_to_data_evar(self): # Add the OCB with electrically scaled variables ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, evar_names=[self.pysat_key], ocb=self.ocb, max_sdiff=self.del_time) @@ -455,6 +465,7 @@ def test_add_ocb_to_data_curl_evar(self): # Add the OCB with curl-scaled variables ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, curl_evar_names=[self.pysat_var2], ocb=self.ocb, max_sdiff=self.del_time) @@ -474,11 +485,12 @@ def test_add_ocb_to_data_evar_vect(self): # Add the OCB with electrically scaled vectors ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, evar_names=['vect_evar'], vector_names={ 'vect_evar': - {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_var2, + {'vect_n': self.pysat_key, + 'vect_e': self.pysat_var2, 'dat_name': 'vect', 'dat_units': 'm/s'}}, ocb=self.ocb, max_sdiff=self.del_time) @@ -504,11 +516,12 @@ def test_add_ocb_to_data_curl_evar_vect(self): # Add the OCB with curl-scaled vectors ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, curl_evar_names=['vect_cevar'], vector_names={ 'vect_cevar': - {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_var2, + {'vect_n': self.pysat_key, + 'vect_e': self.pysat_var2, 'dat_name': 'vect', 'dat_units': 'm/s'}}, ocb=self.ocb, max_sdiff=self.del_time) @@ -534,10 +547,10 @@ def test_add_ocb_to_data_custom_vect(self): # Add the OCB with a custom scaling function ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", - vector_names={ + height_name=self.pysat_alt, vector_names={ 'vect_cust': - {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_var2, + {'vect_n': self.pysat_key, + 'vect_e': self.pysat_var2, 'dat_name': 'vect', 'dat_units': 'm/s', 'scale_func': None}}, @@ -563,12 +576,13 @@ def test_add_ocb_to_data_all_types(self): # Add the OCB with multiple inputs ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, evar_names=[self.pysat_key], curl_evar_names=[self.pysat_var2], vector_names={ 'vect_cust': - {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_var2, + {'vect_n': self.pysat_key, + 'vect_e': self.pysat_var2, 'dat_name': 'vect', 'dat_units': 'm/s', 'scale_func': None}}, @@ -596,7 +610,8 @@ def test_add_ocb_to_data_no_file(self): # Add the OCB without a filename self.ocb_kw['ocbfile'] = None ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", - **self.ocb_kw, max_sdiff=self.del_time) + height_name=self.pysat_alt, **self.ocb_kw, + max_sdiff=self.del_time) self.lwarn = u"no data in Boundary file" self.eval_logging_message() @@ -612,7 +627,7 @@ def test_add_ocb_to_data_bad_hemisphere_selfset(self): with self.assertRaisesRegex( ValueError, 'cannot process observations from both '): ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", - **self.ocb_kw) + height_name=self.pysat_alt, **self.ocb_kw) return def test_bad_pysat_inst(self): @@ -684,8 +699,8 @@ def test_add_ocb_to_data_bad_vector_scale(self): with self.assertRaisesRegex(ValueError, 'missing scaling function for bad'): ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", - vector_names={'bad': {'aacgm_n': 'bad_n', - 'aacgm_e': 'bad_e', + vector_names={'bad': {'vect_n': 'bad_n', + 'vect_e': 'bad_e', 'dat_name': 'bad', 'dat_units': ''}}, ocb=self.ocb) @@ -699,11 +714,12 @@ def test_add_ocb_to_data_bad_vector_name(self): with self.assertRaisesRegex(ValueError, 'unknown vector name bad_n'): ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, evar_names=['bad'], vector_names={ 'bad': - {'aacgm_n': 'bad_n', - 'aacgm_e': self.pysat_key, + {'vect_n': 'bad_n', + 'vect_e': self.pysat_key, 'dat_name': 'bad', 'dat_units': ''}}, ocb=self.ocb) @@ -779,6 +795,7 @@ def setUp(self): # Update the method default self.test_module = pysat.instruments.pysat_testmodel + self.pysat_alt = '' return def test_mismatched_vector_data(self): @@ -789,11 +806,12 @@ def test_mismatched_vector_data(self): with self.assertRaisesRegex(ValueError, 'vector variables must all have the same'): ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, evar_names=['vect_evar'], vector_names={ 'vect_evar': - {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_lat, + {'vect_n': self.pysat_key, + 'vect_e': self.pysat_lat, 'dat_name': 'vect', 'dat_units': 'm/s'}}, ocb=self.ocb, max_sdiff=self.del_time) @@ -824,6 +842,7 @@ def setUp(self): self.test_module = pysat.instruments.pysat_testing self.pysat_var2 = 'dummy2' self.cust_kwargs = {'mlat_name': self.pysat_lat, 'mlt_name': 'mlt', + 'height_name': self.pysat_alt, 'max_sdiff': self.del_time} self.pysat_kw = {} @@ -934,25 +953,25 @@ def test_cust_add_ocb_to_data_vect(self): # Cycle through the different custom inputs for kw, val in [(['evar_names', 'vector_names'], [['vect_evar'], - {'vect_evar': {'aacgm_n': self.pysat_var2, - 'aacgm_e': self.pysat_var2, + {'vect_evar': {'vect_n': self.pysat_var2, + 'vect_e': self.pysat_var2, 'dat_name': 'vect', 'dat_units': 'm/s'}}]), (['curl_evar_names', 'vector_names'], [['vect_cevar'], - {'vect_cevar': {'aacgm_n': self.pysat_var2, - 'aacgm_e': self.pysat_key, + {'vect_cevar': {'vect_n': self.pysat_var2, + 'vect_e': self.pysat_key, 'dat_name': 'vect', 'dat_units': 'm/s'}}]), (['vector_names'], - [{'vect_cust': {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_var2, + [{'vect_cust': {'vect_n': self.pysat_key, + 'vect_e': self.pysat_var2, 'dat_name': 'vect', 'dat_units': 'm/s', 'scale_func': None}}]), (['evar_names', 'curl_evar_names', 'vector_names'], [[self.pysat_var2], [self.pysat_key], - {'vect_cust': {'aacgm_n': self.pysat_key, - 'aacgm_e': self.pysat_key, + {'vect_cust': {'vect_n': self.pysat_key, + 'vect_e': self.pysat_key, 'dat_name': 'vect', 'dat_units': 'm/s', 'scale_func': None}}])]: # Ensure the record index is correct @@ -1022,8 +1041,8 @@ def test_cust_add_ocb_to_data_bad_inputs(self): def test_cust_add_ocb_to_data_bad_vector_scale(self): """Test failure of missing scaling function in custom func.""" - self.cust_kwargs['vector_names'] = {'bad': {'aacgm_n': 'bad_n', - 'aacgm_e': 'bad_e', + self.cust_kwargs['vector_names'] = {'bad': {'vect_n': 'bad_n', + 'vect_e': 'bad_e', 'dat_name': 'bad', 'dat_units': ''}} self.pysat_kw['custom'] = [{'function': ocb_pysat.add_ocb_to_data, @@ -1038,8 +1057,8 @@ def test_cust_add_ocb_to_data_bad_vector_name(self): """Test failure of missing scaling function in custom func.""" self.cust_kwargs['evar_names'] = ['bad'] self.cust_kwargs['vector_names'] = {'bad': - {'aacgm_n': 'bad_n', - 'aacgm_e': self.pysat_key, + {'vect_n': 'bad_n', + 'vect_e': self.pysat_key, 'dat_name': 'bad', 'dat_units': ''}} self.pysat_kw['custom'] = [{'function': ocb_pysat.add_ocb_to_data, @@ -1117,4 +1136,6 @@ def setUp(self): # Update the class defaults self.test_module = pysat.instruments.pysat_testmodel + self.pysat_alt = '' + self.cust_kwargs['height_name'] = self.pysat_alt return From 7070409cd96f8a19d7d1f3dfab7da0066b3a7921 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 2 May 2024 14:54:50 -0400 Subject: [PATCH 37/60] DOC: update pysat example Update the pysat example to: - use new geographic inputs, - plot relative to the EAB appropriately, and - update the matplotlib usage. --- docs/examples/ex_pysat_eab.rst | 84 ++++++++---------- .../example_vtec_eab_north_location.png | Bin 83043 -> 373090 bytes 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/docs/examples/ex_pysat_eab.rst b/docs/examples/ex_pysat_eab.rst index f1bd78e3..716c8160 100644 --- a/docs/examples/ex_pysat_eab.rst +++ b/docs/examples/ex_pysat_eab.rst @@ -88,96 +88,81 @@ using the following commands. prodedures while loading the desired data. The :py:mod:`ocbpy.instrument.pysat_instrument` module contains functions that may be applied using the :py:mod:`pysat` `custom interface -`_. -However, before this can be done the magnetic locations need to be calculated. -This can be done by writing an appropriate function that takes the -:py:class:`pysat.Instrument` object as input and updates it within the function. - +`_. The +EAB conversion can handle magnetic, geodetic, or geocentric coordinates for +scalar or vector data types using the :py:var:`loc_coord` and +:py:var:`vect_coord` keyword arguments, respectively. We do need to calculate +local time before calculating the EAB coordinates, though. :: - import aacgmv2 - import numpy as np - - def add_mag_coords(inst, lat='gdlat', lon='glon', alt='gdalt'): - """Add AACGMV2 magnetic coordinates. + def add_slt(inst, lon='glon', slt='slt'): + """Add solar local time. Parameters ---------- inst : pysat.Instrument Data object - lat : str - Geodetic latitude key (default='gdlat') lon : str Geographic longitude key (default='glon') - alt : str - Geodetic altitude key (default='gdalt') + slt : str + Key for new solar local tim data (default='slt') + """ # Initalize the data arrays - mlat = np.full(shape=tuple(tec.data.dims[kk] - for kk in ['time', lat, lon]), - fill_value=np.nan) - mlt = np.full(shape=mlat.shape, fill_value=np.nan) - - # Cycle through all times, calculating magnetic locations - for i, dtime in enumerate(inst.index): - for j, gdlat in enumerate(inst[lat].values): - height = inst[alt][i, j].values - if not np.isnan(height).all(): - mlat[i, j], mlon, r = aacgmv2.convert_latlon_arr( - gdlat, inst[lon].values, height, dtime) - mlt[i, j] = aacgmv2.convert_mlt(mlon, dtime) - - # Assign the magnetic data to the input Instrument - inst.data = inst.data.assign({"mlat": (("time", lat, lon), mlat), - "mlt": (("time", lat, lon), mlt)}) + lt = [ocbpy.ocb_time.glon2slt(inst[lon], dtime) for dtime in inst.index] + + # Assign the SLT data to the input Instrument + inst.data = inst.data.assign({slt: (("time", lon), lt)}) return -Assign this function and the ocbpy function in the desired order of operations. +Assign this function and the :py:mod:`ocbpy` function in the desired order of +operations. When calculating magnetic coordinates, it is important to specify +the height, which can be a single value or specified for each observation. :: - - tec.custom_attach(add_mag_coords) + tec.custom_attach(add_slt, kwargs={'lon': 'glon'}) tec.custom_attach(ocbpy.instruments.pysat_instruments.add_ocb_to_data, - kwargs={'ocb': eab, 'mlat_name': 'mlat', - 'mlt_name': 'mlt', 'max_sdiff': 150}) + kwargs={'ocb': eab, 'mlat_name': 'gdlat', + 'mlt_name': 'slt', 'height_name': 'gdalt', + 'loc_coord': 'geodetic', 'max_sdiff': 150}) tec.load(date=eab.dtime[eab.rec_ind]) print(tec.variables) - ['time', 'gdlat', 'glon', 'dtec', 'gdalt', 'tec', 'mlat', 'mlt', 'mlat_ocb', - 'mlt_ocb', 'r_corr_ocb'] + ['time', 'gdlat', 'glon', 'dtec', 'gdalt', 'tec', 'gdlat_eab', + 'slt_eab', 'r_corr_eab'] Now we have the EAB coordinates for each location in the Northern Hemisphere where a good EAB was found within 2.5 minutes of the data record. This time difference was chosen because the VTEC data has a 5 minute resolution. -Now, let's plot the average of the VTEC poleward of the EAB. To do this we will -first need to calculate these averages. +Now, let's plot the average of the VTEC equatorward of the EAB. To do this we +will first need to calculate these averages. :: del_lat = 2.0 del_mlt = 2.0 - ave_lat = np.arange(eab.boundary_lat, 90, del_lat) + ave_lat = np.arange(45, eab.boundary_lat, del_lat) ave_mlt = np.arange(0, 24, del_mlt) ave_tec = np.full(shape=tec['tec'].shape, fill_value=np.nan) for lat in ave_lat: for mlt in ave_mlt: # We are not overlapping bins, so don't need to worry about MLT - # rollover from 0-24 + # rollover from 0-24 sel_tec = tec['tec'].where( - (tec['mlat_ocb'] > lat) & (tec['mlat_ocb'] <= lat + del_lat) - & (tec['mlt_ocb'] >= mlt) & (tec['mlt_ocb'] < mlt + del_mlt)) + (tec['gdlat_eab'] > lat) & (tec['gdlat_eab'] <= lat + del_lat) + & (tec['slt_eab'] >= mlt) & (tec['slt_eab'] < mlt + del_mlt)) inds = np.where(~np.isnan(sel_tec.values)) if len(inds[0]) > 0: ave_tec[inds] = np.nanmean(sel_tec.values) Now let us plot these averages at the EAB location of each measurement. This will provide us with knowledge of the coverage as well as knowledge of the -average behaviour. +average behaviour of the sub-auroral VTEC on this day. :: @@ -200,11 +185,12 @@ average behaviour. ax.plot(lon, lat, "m-", linewidth=2, label="EAB") # Plot the VTEC data - tec_lon = tec['mlt_ocb'].values * np.pi / 12.0 - tec_lat = 90.0 - tec['mlat_ocb'].values + tec_lon = (tec['slt_eab'].values * np.pi / 12.0) + tec_lat = (90.0 - tec['gdlat_eab'].values) tec_max = np.ceil(np.nanmax(ave_tec)) - con = ax.scatter(tec_lon, tec_lat, c=ave_tec, marker="s", - cmap=mpl.cm.get_cmap("viridis"), s=5, vmin=0, vmax=tec_max) + con = ax.scatter(tec_lon, tec_lat, c=ave_tec.transpose([0, 2, 1]), + marker="s", cmap=mpl.colormaps["viridis"], s=5, + vmin=0, vmax=tec_max) # Add a colourbar and labels tticks = np.linspace(0, tec_max, 6, endpoint=True) diff --git a/docs/figures/example_vtec_eab_north_location.png b/docs/figures/example_vtec_eab_north_location.png index d4eaa6471943121925d9e2bafdf0b5bd9697f0d9..cf138c297b3ac1b1d4ceba573413c6d7f5cfd68f 100644 GIT binary patch literal 373090 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%0Ahs=*sLNzk;IP= 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

I`f^A0mpCkAZT9^x;QWIYWGwjGgRv!KXGf}a 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 literal 83043 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

5jf*`c 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 From be352bf5f0c09d853715da0854dc94752bd2741b Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 2 May 2024 14:55:23 -0400 Subject: [PATCH 38/60] DOC: update the changelog Expand the changes made in this pull reqest. --- Changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index cf7ac09e..907fd922 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -17,7 +17,7 @@ Summary of all changes made since the first stable release * ENH: Allow data padding in `pysat_instrument` functions * ENH: Created separate vector transformation functions to support multiple coordinate systems -* ENH: Updated VectorData to allow geographic vector inputs +* ENH: Updated VectorData and pysat_instruments to allow geographic inputs * BUG: Fixed a typo in the documentation's pysat example * BUG: Added an error catch for badly formatted SuperMAG file reading * BUG: Fixed the flat/zero masking for vector pole angles with array input @@ -25,6 +25,7 @@ Summary of all changes made since the first stable release * TST: Reduced duplication by creating a common test class and test variable * TST: Added a ReadTheDocs yaml * DOC: Improved docstring style compliance and correctness +* DOC: Updated pysat example to use geodetic inputs and improve the EAB plot 0.3.0 (10-21-2022) ------------------ From 46db20410f7716723f81978452f9214d43bd4127 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Thu, 2 May 2024 14:56:23 -0400 Subject: [PATCH 39/60] ENH: update pysat variable names Update the pysat variable names to reflect the type of boundary used. --- ocbpy/instruments/pysat_instruments.py | 109 +++++++++++++++---------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/ocbpy/instruments/pysat_instruments.py b/ocbpy/instruments/pysat_instruments.py index 1d5d237a..bde5b798 100644 --- a/ocbpy/instruments/pysat_instruments.py +++ b/ocbpy/instruments/pysat_instruments.py @@ -152,10 +152,37 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Ensure the correct data format max_sdiff = int(max_sdiff) + # Load the OCB data for the data period, if desired + if ocb is None or (not isinstance(ocb, ocbpy.OCBoundary) + and not isinstance(ocb, ocbpy.DualBoundary)): + dstart = pysat_inst.index[0] - dt.timedelta(seconds=max_sdiff + 1) + dend = pysat_inst.index[-1] + dt.timedelta(seconds=max_sdiff + 1) + + # If hemisphere isn't specified, set it here + if hemisphere == 0: + hemisphere = np.sign(np.nanmax(lat)) + + # Ensure that all data is in the same hemisphere + if hemisphere == 0: + hemisphere = np.sign(np.nanmin(lat)) + elif hemisphere != np.sign(np.nanmin(lat)): + raise ValueError("".join(["cannot process observations from " + "both hemispheres at the same time;" + "set hemisphere=+/-1 to choose."])) + + # Initialize the OCBoundary object + ocb = ocbpy.OCBoundary(ocbfile, stime=dstart, etime=dend, + instrument=instrument, hemisphere=hemisphere) + elif hemisphere == 0: + # If the OCBoundary object is specified and hemisphere isn't use + # the OCBoundary object to specify the hemisphere + hemisphere = ocb.hemisphere + # Format the new data column names - olat_name = "{:s}_ocb".format(mlat_name) - omlt_name = "{:s}_ocb".format(mlt_name) - ocor_name = "r_corr_ocb" + bname = ocb.__class__.__name__.split('oundary')[0].lower() + olat_name = "_".join([mlat_name, bname]) + omlt_name = "_".join([mlt_name, bname]) + ocor_name = "_".join(["r", "corr", bname]) ocb_names = [olat_name, omlt_name, ocor_name] ocb_vect_attrs = ['ocb_n', 'ocb_e', 'ocb_z', 'ocb_mag', 'unscaled_r', 'scaled_r'] @@ -194,7 +221,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', raise ValueError('missing scaling function for {:}'.format( eattr)) - oattr = "{:s}_ocb".format(eattr) + oattr = "{:s}_{:s}".format(eattr, bname) if oattr not in ocb_names: ocb_names.append(oattr) @@ -237,32 +264,6 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', else: height = np.asarray(height) - # Load the OCB data for the data period, if desired - if ocb is None or (not isinstance(ocb, ocbpy.OCBoundary) - and not isinstance(ocb, ocbpy.DualBoundary)): - dstart = pysat_inst.index[0] - dt.timedelta(seconds=max_sdiff + 1) - dend = pysat_inst.index[-1] + dt.timedelta(seconds=max_sdiff + 1) - - # If hemisphere isn't specified, set it here - if hemisphere == 0: - hemisphere = np.sign(np.nanmax(lat)) - - # Ensure that all data is in the same hemisphere - if hemisphere == 0: - hemisphere = np.sign(np.nanmin(lat)) - elif hemisphere != np.sign(np.nanmin(lat)): - raise ValueError("".join(["cannot process observations from " - "both hemispheres at the same time;" - "set hemisphere=+/-1 to choose."])) - - # Initialize the OCBoundary object - ocb = ocbpy.OCBoundary(ocbfile, stime=dstart, etime=dend, - instrument=instrument, hemisphere=hemisphere) - elif hemisphere == 0: - # If the OCBoundary object is specified and hemisphere isn't use - # the OCBoundary object to specify the hemisphere - hemisphere = ocb.hemisphere - # Ensure all data is from one hemisphere and is finite if pysat_inst.pandas_format: finite_mask = ((np.sign(lat) == hemisphere) @@ -306,17 +307,37 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', combo_shape.append(pysat_inst[lat_coord].shape[0]) ocb_coords.append(lat_coord) - if height_name in pysat_inst.variables: - for alt_coord in pysat_inst[height_name].coords: - if alt_coord not in pysat_inst[mlt_name].coords: - combo_shape.append(pysat_inst[alt_coord].shape[0]) - ocb_coords.append(alt_coord) - - # Reshape the data - out_lat, out_lt, out_height = np.meshgrid(lat, lt, height) + # Reshape the latitude and local time data + out_lat, out_lt = np.meshgrid(lat, lt) lat = out_lat.reshape(combo_shape) lt = out_lt.reshape(combo_shape) - height = out_height.reshape(combo_shape) + + # Determine if reshaping for altitude is necessary + if len(height.shape) == 0: + height = np.full(shape=lat.shape, fill_value=height) + elif height.shape != lat.shape: + if height_name in pysat_inst.variables: + new_coords = False + for alt_coord in pysat_inst[height_name].coords: + if alt_coord not in ocb_coords: + combo_shape.append(pysat_inst[alt_coord].shape[0]) + ocb_coords.append(alt_coord) + new_coords = True + + # Reshape the data + if new_coords: + out_lat, out_lt, out_height = np.meshgrid(lat, lt, height) + lat = out_lat.reshape(combo_shape) + lt = out_lt.reshape(combo_shape) + height = out_height.reshape(combo_shape) + else: + height = height.reshape(combo_shape) + elif len(height.shape) == len(lat.shape): + # Try and reshape the height + height = height.reshape(combo_shape) + else: + # Can't reshape the height + raise ValueError('unexpected height shape') else: ocb_coords = [pysat_inst.index.name] @@ -344,7 +365,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', if eattr in vector_names.keys(): for vattr in ocb_vect_attrs: ovattr = '_'.join([oattr, vattr]) - ovattr = ovattr.replace('ocb_ocb_', 'ocb_') + ovattr = ovattr.replace('_ocb_', '_') ocb_output[ovattr] = np.full(lat.shape, np.nan, dtype=float) else: ocb_output[oattr] = np.full(lat.shape, np.nan, dtype=float) @@ -414,7 +435,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', vshape = list() for eattr in vector_names.keys(): - oattr = "{:s}_ocb".format(eattr) + oattr = "{:s}_{:s}".format(eattr, bname) for ikey in vector_names[eattr].keys(): # Not all vector names are DataFrame names vname = vector_names[eattr][ikey] @@ -444,7 +465,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Assign the vector attributes to the output for vattr in ocb_vect_attrs: ovattr = '_'.join([oattr, vattr]) - ovattr = ovattr.replace('ocb_ocb_', 'ocb_') + ovattr = ovattr.replace('_ocb_', '_') ocb_output[ovattr][iout] = getattr(vout, vattr) if hasattr(ocb, "ocb"): @@ -459,7 +480,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', (evar_names, ocbscal.normal_evar), (curl_evar_names, ocbscal.normal_curl_evar)]: for eattr in scale_names: - oattr = "{:s}_ocb".format(eattr) + oattr = "{:s}_{:s}".format(eattr, bname) if time_mask is None: evar = pysat_inst[eattr][iout] else: @@ -486,7 +507,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Update the pysat Metadata eattr = oattr.split('_ocb')[0] if hasattr(ocb, "instrument"): - notes = "".join(["OCB obtained from ", ocb.instrument, + notes = "".join(["Boundary obtained from ", ocb.instrument, " data in file ", ocb.filename, "using a boundary latitude of ", "{:.2f}".format(ocb.boundary_lat)]) From 4876bc26c746362307c95f5d28b9c5f80a4ce3c5 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 16:45:46 -0400 Subject: [PATCH 40/60] TST: rename test files Rename test files to better conform to real file names. --- ocbpy/tests/test_data/{test_north_circle => test_north_ocb} | 0 ocbpy/tests/test_data/{test_south_circle => test_south_ocb} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename ocbpy/tests/test_data/{test_north_circle => test_north_ocb} (100%) rename ocbpy/tests/test_data/{test_south_circle => test_south_ocb} (100%) diff --git a/ocbpy/tests/test_data/test_north_circle b/ocbpy/tests/test_data/test_north_ocb similarity index 100% rename from ocbpy/tests/test_data/test_north_circle rename to ocbpy/tests/test_data/test_north_ocb diff --git a/ocbpy/tests/test_data/test_south_circle b/ocbpy/tests/test_data/test_south_ocb similarity index 100% rename from ocbpy/tests/test_data/test_south_circle rename to ocbpy/tests/test_data/test_south_ocb From 88a71407eaf3923d044e2ca07ed80092912f8c4a Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 16:46:24 -0400 Subject: [PATCH 41/60] TST: update test filenames Update test filenames to conform with name change. --- ocbpy/tests/test_boundary_dual.py | 4 ++-- ocbpy/tests/test_boundary_eab.py | 4 ++-- ocbpy/tests/test_boundary_ocb.py | 16 ++++++++-------- ocbpy/tests/test_cycle_boundary.py | 6 +++--- ocbpy/tests/test_general.py | 4 ++-- ocbpy/tests/test_ocb_scaling.py | 10 +++++----- ocbpy/tests/test_supermag.py | 2 +- ocbpy/tests/test_vort.py | 6 +++--- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ocbpy/tests/test_boundary_dual.py b/ocbpy/tests/test_boundary_dual.py index b0ccb4e9..494e8575 100644 --- a/ocbpy/tests/test_boundary_dual.py +++ b/ocbpy/tests/test_boundary_dual.py @@ -89,7 +89,7 @@ def setUp(self): "dmsp-ssj_north_out.eab"), "ocb_instrument": "image", "ocb_filename": path.join(cc.test_dir, - "test_north_circle")}, + "test_north_ocb")}, {"eab_instrument": "dmsp-ssj", "hemisphere": 1, "eab_filename": path.join(cc.test_dir, "dmsp-ssj_north_out.eab"), @@ -101,7 +101,7 @@ def setUp(self): "dmsp-ssj_south_out.eab"), "ocb_instrument": "ampere", "ocb_filename": path.join(cc.test_dir, - "test_south_circle")}] + "test_south_ocb")}] return diff --git a/ocbpy/tests/test_boundary_eab.py b/ocbpy/tests/test_boundary_eab.py index d538dbed..bf314cab 100644 --- a/ocbpy/tests/test_boundary_eab.py +++ b/ocbpy/tests/test_boundary_eab.py @@ -24,7 +24,7 @@ def setUp(self): self.test_class = ocbpy.EABoundary self.inst_init = {"instrument": "image", "hemisphere": 1, "filename": path.join(cc.test_dir, - "test_north_circle")} + "test_north_ocb")} return @@ -47,7 +47,7 @@ def setUp(self): "r_err"]} self.inst_init = [{"instrument": "image", "hemisphere": 1, "filename": path.join(cc.test_dir, - "test_north_circle")}, + "test_north_ocb")}, {"instrument": "dmsp-ssj", "hemisphere": 1, "filename": path.join(cc.test_dir, "dmsp-ssj_north_out.eab")}, diff --git a/ocbpy/tests/test_boundary_ocb.py b/ocbpy/tests/test_boundary_ocb.py index ab118353..0c3897f7 100644 --- a/ocbpy/tests/test_boundary_ocb.py +++ b/ocbpy/tests/test_boundary_ocb.py @@ -23,7 +23,7 @@ def setUp(self): self.test_class = ocbpy.OCBoundary self.inst_init = {"instrument": "image", "hemisphere": 1, "filename": path.join(cc.test_dir, - "test_north_circle")} + "test_north_ocb")} return @@ -101,7 +101,7 @@ def test_bad_time_structure(self): # Set the filename ocb.filename = path.join(path.dirname(ocbpy.__file__), "tests", - "test_data", "test_north_circle") + "test_data", "test_north_ocb") self.assertTrue(path.isfile(ocb.filename)) # Load the data, skipping the year @@ -133,7 +133,7 @@ def setUp(self): "r_err"]} self.inst_init = [{"instrument": "image", "hemisphere": 1, "filename": path.join(cc.test_dir, - "test_north_circle")}, + "test_north_ocb")}, {"instrument": "dmsp-ssj", "hemisphere": 1, "filename": path.join(cc.test_dir, "dmsp-ssj_north_out.ocb")}, @@ -142,7 +142,7 @@ def setUp(self): "dmsp-ssj_south_out.ocb")}, {"instrument": "ampere", "hemisphere": -1, "filename": path.join(cc.test_dir, - "test_south_circle")}] + "test_south_ocb")}] self.ocb = None return @@ -191,7 +191,7 @@ def setUp(self): self.set_empty = {"filename": path.join(cc.test_dir, "test_empty"), "instrument": "image"} self.set_default = {"filename": path.join(cc.test_dir, - "test_north_circle"), + "test_north_ocb"), "instrument": "image"} self.ocb = None return @@ -346,7 +346,7 @@ def setUp(self): self.test_class = ocbpy.OCBoundary self.ref_boundary = 74.0 self.set_north = {'filename': path.join(cc.test_dir, - "test_north_circle"), + "test_north_ocb"), 'instrument': 'image'} self.mlt = numpy.linspace(0.0, 24.0, num=6) @@ -766,7 +766,7 @@ def setUp(self): self.test_class = ocbpy.OCBoundary self.ref_boundary = -74.0 self.set_south = {"filename": path.join(cc.test_dir, - "test_south_circle"), + "test_south_ocb"), "instrument": "ampere", "hemisphere": -1, "rfunc": ocbpy.ocb_correction.circular} @@ -1116,7 +1116,7 @@ def test_bad_instrument_input(self): """Test failure when bad instrument value is input.""" test_north = path.join(path.dirname(ocbpy.__file__), "tests", - "test_data", "test_north_circle") + "test_data", "test_north_ocb") self.assertTrue(path.isfile(test_north)) with self.assertRaisesRegex(ValueError, "unknown instrument"): self.test_class(instrument="hi", filename=test_north) diff --git a/ocbpy/tests/test_cycle_boundary.py b/ocbpy/tests/test_cycle_boundary.py index cb258a3a..da5593ae 100644 --- a/ocbpy/tests/test_cycle_boundary.py +++ b/ocbpy/tests/test_cycle_boundary.py @@ -21,7 +21,7 @@ class TestCycleMatchData(cc.TestLogWarnings): def setUp(self): """Initialize the test environment.""" self.ocb = ocbpy.OCBoundary(filename=path.join(cc.test_dir, - "test_north_circle"), + "test_north_ocb"), instrument="image", hemisphere=1) self.ocb.rec_ind = -1 self.idat = 0 @@ -237,7 +237,7 @@ def setUp(self): # Alter the test environment self.ocb = ocbpy.DualBoundary( - ocb_filename=path.join(cc.test_dir, "test_north_circle"), + ocb_filename=path.join(cc.test_dir, "test_north_ocb"), ocb_instrument="image", eab_instrument='image', hemisphere=1, eab_filename=path.join(cc.test_dir, "test_north_eab")) self.ocb.rec_ind = -1 @@ -252,7 +252,7 @@ class TestCycleGoodIndices(unittest.TestCase): def setUp(self): """Initialize the test environment.""" self.ocb = ocbpy.OCBoundary( - filename=path.join(cc.test_dir, "test_north_circle"), + filename=path.join(cc.test_dir, "test_north_ocb"), instrument="image", hemisphere=1) self.ocb.rec_ind = -1 self.test_func = ocbpy.cycle_boundary.retrieve_all_good_indices diff --git a/ocbpy/tests/test_general.py b/ocbpy/tests/test_general.py index cb99b298..a71f1d2c 100644 --- a/ocbpy/tests/test_general.py +++ b/ocbpy/tests/test_general.py @@ -19,7 +19,7 @@ def setUp(self): """Set up the test environment.""" super().setUp() - self.test_file = os.path.join(cc.test_dir, "test_north_circle") + self.test_file = os.path.join(cc.test_dir, "test_north_ocb") self.temp_output = os.path.join(cc.test_dir, "temp_gen") self.rstat = None return @@ -83,7 +83,7 @@ def setUp(self): """Set up a clean test environment.""" super().setUp() - self.test_file_soy = os.path.join(cc.test_dir, "test_north_circle") + self.test_file_soy = os.path.join(cc.test_dir, "test_north_ocb") self.test_file_dt = os.path.join(cc.test_dir, "dmsp-ssj_north_out.ocb") self.test_file_sod = os.path.join(cc.test_dir, "test_sod") self.headers = {self.test_file_soy: diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index c796d945..8b56c0e0 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -225,7 +225,7 @@ def setUp(self): ocbpy.logger.setLevel(logging.INFO) # Initialize the testing variables - test_file = path.join(cc.test_dir, "test_north_circle") + test_file = path.join(cc.test_dir, "test_north_ocb") self.assertTrue(path.isfile(test_file)) self.ocb = ocbpy.OCBoundary(filename=test_file, instrument='image') self.ocb.rec_ind = 27 @@ -276,7 +276,7 @@ class TestOCBScalingMethods(unittest.TestCase): def setUp(self): """Initialize the OCBoundary and VectorData objects.""" - test_file = path.join(cc.test_dir, "test_north_circle") + test_file = path.join(cc.test_dir, "test_north_ocb") self.assertTrue(path.isfile(test_file)) self.ocb = ocbpy.OCBoundary(filename=test_file, instrument='image') self.ocb.rec_ind = 27 @@ -806,7 +806,7 @@ def setUp(self): self.ocb = ocbpy.DualBoundary( eab_filename=path.join(cc.test_dir, "test_north_eab"), eab_instrument='image', ocb_instrument='image', hemisphere=1, - ocb_filename=path.join(cc.test_dir, "test_north_circle")) + ocb_filename=path.join(cc.test_dir, "test_north_ocb")) self.ocb_attrs = ['ocb_lat', 'ocb_mlt', 'r_corr', 'ocb_n', 'ocb_e', 'ocb_z'] @@ -933,7 +933,7 @@ class TestVectorDataRaises(unittest.TestCase): def setUp(self): """Initialize the tests for calc_vec_pole_angle.""" - test_file = path.join(cc.test_dir, "test_north_circle") + test_file = path.join(cc.test_dir, "test_north_ocb") self.assertTrue(path.isfile(test_file)) self.ocb = ocbpy.OCBoundary(filename=test_file, instrument='image') self.ocb.rec_ind = 27 @@ -1272,7 +1272,7 @@ class TestOCBScalingArrayMethods(unittest.TestCase): def setUp(self): """Set up the test environment.""" - test_file = path.join(cc.test_dir, "test_north_circle") + test_file = path.join(cc.test_dir, "test_north_ocb") self.ocb = ocbpy.OCBoundary(filename=test_file, instrument='image') # Construct a set of test vectors that have all the different OCB diff --git a/ocbpy/tests/test_supermag.py b/ocbpy/tests/test_supermag.py index d8ed0774..d5e83c8f 100644 --- a/ocbpy/tests/test_supermag.py +++ b/ocbpy/tests/test_supermag.py @@ -21,7 +21,7 @@ class TestSuperMAG2AsciiMethods(unittest.TestCase): def setUp(self): """Initialize the setup for SuperMAG processing unit tests.""" - self.test_ocb = os.path.join(cc.test_dir, "test_north_circle") + self.test_ocb = os.path.join(cc.test_dir, "test_north_ocb") self.test_eab = os.path.join(cc.test_dir, "test_north_eab") self.test_file = os.path.join(cc.test_dir, "test_hemi_smag") self.test_eq_file = os.path.join(cc.test_dir, "test_eq_smag") diff --git a/ocbpy/tests/test_vort.py b/ocbpy/tests/test_vort.py index 38c8007a..666084ef 100644 --- a/ocbpy/tests/test_vort.py +++ b/ocbpy/tests/test_vort.py @@ -25,7 +25,7 @@ def setUp(self): super().setUp() self.test_file = os.path.join(cc.test_dir, "test_vort") - self.test_ocb = os.path.join(cc.test_dir, "test_north_circle") + self.test_ocb = os.path.join(cc.test_dir, "test_north_ocb") self.temp_output = os.path.join(cc.test_dir, "temp_vort") self.assertTrue(os.path.isfile(self.test_file), msg="'{:}' is not a file".format(self.test_file)) @@ -88,7 +88,7 @@ class TestVort2AsciiMethods(unittest.TestCase): def setUp(self): """Initialize the testing set up.""" - self.test_ocb = os.path.join(cc.test_dir, "test_north_circle") + self.test_ocb = os.path.join(cc.test_dir, "test_north_ocb") self.test_eab = os.path.join(cc.test_dir, "test_north_eab") self.test_file = os.path.join(cc.test_dir, "test_hemi_vort") self.test_eq_file = os.path.join(cc.test_dir, "test_eq_hemi_vort") @@ -248,7 +248,7 @@ def setUp(self): self.ocb_dir = os.path.dirname(ocbpy.__file__) self.test_ocb = os.path.join(self.ocb_dir, "tests", "test_data", - "test_north_circle") + "test_north_ocb") self.test_file = os.path.join(self.ocb_dir, "tests", "test_data", "test_vort") self.bad_file = os.path.join(self.ocb_dir, "test", "test_data", From 1faf7fd0b5dde0f2dfbb7609ebb68df10315ba9a Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 16:50:46 -0400 Subject: [PATCH 42/60] ENH: added geo coords to pysat Allow geographic coordinates to be used by pysat Instruments. --- ocbpy/instruments/pysat_instruments.py | 125 +++++++++++++++++++------ 1 file changed, 94 insertions(+), 31 deletions(-) diff --git a/ocbpy/instruments/pysat_instruments.py b/ocbpy/instruments/pysat_instruments.py index bde5b798..8592cda9 100644 --- a/ocbpy/instruments/pysat_instruments.py +++ b/ocbpy/instruments/pysat_instruments.py @@ -11,7 +11,9 @@ """ import datetime as dt +import inspect import numpy as np +import os try: import pysat @@ -26,7 +28,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', evar_names=None, curl_evar_names=None, vector_names=None, - height=350.0, hemisphere=0, ocb=None, ocbfile='default', + height=350.0, hemisphere=0, ocb=None, ocbfile='ocb', instrument='', max_sdiff=60, min_merit=None, max_merit=None, loc_coord='magnetic', vect_coord='magnetic', **kwargs): """Covert the location of pysat data into OCB, EAB, or Dual coordinates. @@ -67,7 +69,9 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', looks to `ocbfile` and creates an OCBoundary object. (default=None) ocbfile : str file containing the required OC boundary data sorted by time, ignorned - if OCBoundary object supplied (default='default') + if OCBoundary object supplied. To use the default for a boundary type, + supply the desired boundary type; 'eab', 'ocb', or 'dual'. + (default='ocb') instrument : str Instrument providing the OCBoundaries. Requires 'image' or 'ampere' if a file is provided. If using filename='default', also accepts @@ -91,10 +95,12 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', `height_name` or `height` will be used to convert the data to magnetic coordinates. (default='magnetic') kwargs : dict - Dict with optional selection criteria. The key should correspond to a - data attribute and the value must be a tuple with the first value - specifying 'max', 'min', 'maxeq', 'mineq', or 'equal' and the second - value specifying the value to use in the comparison. + Dict with optional selection criteria or criteria for initializing a + DualBoundary class object (in combination with `ocb=None` and + `ocbfile='dual'`). For the optional selection criteria, the key should + correspond to a data attribute and the value must be a tuple with the + first value specifying 'max', 'min', 'maxeq', 'mineq', or 'equal' and + the second value specifying the value to use in the comparison. Raises ------ @@ -152,8 +158,14 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Ensure the correct data format max_sdiff = int(max_sdiff) + # Extract the locations as numpy arrays + lat = np.array(pysat_inst[mlat_name]) + lt = np.array(pysat_inst[mlt_name]) + ndat = len(lat) + # Load the OCB data for the data period, if desired if ocb is None or (not isinstance(ocb, ocbpy.OCBoundary) + and not isinstance(ocb, ocbpy.EABoundary) and not isinstance(ocb, ocbpy.DualBoundary)): dstart = pysat_inst.index[0] - dt.timedelta(seconds=max_sdiff + 1) dend = pysat_inst.index[-1] + dt.timedelta(seconds=max_sdiff + 1) @@ -170,9 +182,52 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', "both hemispheres at the same time;" "set hemisphere=+/-1 to choose."])) - # Initialize the OCBoundary object - ocb = ocbpy.OCBoundary(ocbfile, stime=dstart, etime=dend, - instrument=instrument, hemisphere=hemisphere) + # Determine the boundary type by filename, if possible + if ocbfile is None: + fileroot = "" + else: + fileroot = os.path.split(ocbfile)[-1].lower() + + if ocbfile.lower() in ['eab', 'ocb', 'dual']: + ocbfile = "default" + + if fileroot.find("ocb") > 0 and fileroot.find("eab") < 0: + # Initialize the OCBoundary object + ocb = ocbpy.OCBoundary(ocbfile, stime=dstart, etime=dend, + instrument=instrument, hemisphere=hemisphere) + elif fileroot.find("ocb") < 0 and fileroot.find("eab") > 0: + # Initialize the EABoundary object + ocb = ocbpy.EABoundary(ocbfile, stime=dstart, etime=dend, + instrument=instrument, hemisphere=hemisphere) + elif fileroot == 'dual': + # This works by assigning default to both, or allowing additional + # inputs through `kwargs` + init_keys = {'hemisphere': hemisphere, 'stime': dstart, + 'etime': dend} + if len(kwargs.keys()) > 0: + sig = inspect.getfullargspec(ocbpy.DualBoundary.__init__) + for key in kwargs.keys(): + if key in sig.args: + init_keys[key] = kwargs[key] + + # Clean DualBoundary kwargs from kwarg input + for key in init_keys: + if key in kwargs.keys(): + del kwargs[key] + + if 'ocb_instrument' not in init_keys.keys(): + init_keys['ocb_instrument'] = instrument + + if 'eab_instrument' not in init_keys.keys(): + init_keys['eab_instrument'] = instrument + + # Initialize the dual-boundary object + ocb = ocbpy.DualBoundary(**init_keys) + else: + # Can't determine desired boundary type + raise ValueError("".join(["can't determine desired boundary type ", + "from filename: ", repr(ocbfile)])) + elif hemisphere == 0: # If the OCBoundary object is specified and hemisphere isn't use # the OCBoundary object to specify the hemisphere @@ -248,15 +303,10 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Append the remaining OCB output names for eattr in evar_names: - ocb_names.append("{:s}_ocb".format(eattr)) + ocb_names.append("{:s}_{:s}".format(eattr, bname)) for eattr in curl_evar_names: - ocb_names.append("{:s}_ocb".format(eattr)) - - # Extract the locations as numpy arrays - lat = np.array(pysat_inst[mlat_name]) - lt = np.array(pysat_inst[mlt_name]) - ndat = len(lat) + ocb_names.append("{:s}_{:s}".format(eattr, bname)) # Extract the height, if possible if height_name in pysat_inst.variables: @@ -361,11 +411,13 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Initialise the OCB output ocb_output = dict() for oattr in ocb_names: - eattr = oattr[:-4] + eattr = oattr[:-1 * len(bname) - 1] if eattr in vector_names.keys(): for vattr in ocb_vect_attrs: ovattr = '_'.join([oattr, vattr]) ovattr = ovattr.replace('_ocb_', '_') + if bname != "ocb": + ovattr = ovattr.replace("ocb", bname) ocb_output[ovattr] = np.full(lat.shape, np.nan, dtype=float) else: ocb_output[oattr] = np.full(lat.shape, np.nan, dtype=float) @@ -505,7 +557,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', pysat_inst.data = pysat_inst.data.assign(set_data) # Update the pysat Metadata - eattr = oattr.split('_ocb')[0] + eattr = oattr.split('_{:s}'.format(bname))[0] if hasattr(ocb, "instrument"): notes = "".join(["Boundary obtained from ", ocb.instrument, " data in file ", ocb.filename, @@ -527,7 +579,7 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', else: func_name = vector_names[eattr]['scale_func'].__name__ notes += " and was scaled using {:}".format(func_name) - eattr = vector_attrs['_'.join([eattr, 'ocb'])][0] + eattr = vector_attrs['_'.join([eattr, bname])][0] isvector = True else: isvector = False @@ -587,13 +639,13 @@ def add_ocb_to_metadata(pysat_inst, ocb_name, pysat_name, overwrite=False, pysat_inst : pysat.Instrument pysat.Instrument class object containing magnetic coordinates ocb_name : str - Data column name for OCB data + Data column name for boundary data pysat_name : str - Data column name for non-OCB version of this data + Data column name for non-adaptive boundary version of this data overwrite : bool Overwrite existing metadata, if present (default=False) notes : str) - Notes about this OCB data (default='') + Notes about this boundary data (default='') isvector : bool Is this vector data or not (default=False) @@ -603,30 +655,42 @@ def add_ocb_to_metadata(pysat_inst, ocb_name, pysat_name, overwrite=False, If input pysat Instrument object is the wrong class """ + bound_desc = {"ocb": "Open Closed field-line Boundary", + "eab": "Equatorward Auroral Boundary", + "dualb": "Dual Boundary", "": ""} + + bname = "" + for bkey in ocb_name.split("_"): + if len(bkey) > 0 and bkey in bound_desc.keys(): + bname = bkey + break + + bextra = "" if len(bname) == 0 else "{:s}_".format(bname.upper()) # Test the input if not isinstance(pysat_inst, pysat.Instrument): raise ValueError('unknown class, expected pysat.Instrument') if not overwrite and ocb_name in pysat_inst.meta.data.index: - ocbpy.logger.warning("OCB data already has metadata") + ocbpy.logger.warning("Boundary data already has metadata") else: if pysat_name not in pysat_inst.meta.data.index: - name = ("OCB_" + ocb_name.split("_ocb")[0]).replace("_", " ") + name = (bextra + ocb_name.split("_{:s}".format(bname))[0]).replace( + "_", " ") new_meta = {pysat_inst.meta.labels.fill_val: np.nan, pysat_inst.meta.labels.name: name, pysat_inst.meta.labels.desc: name.replace( - "OCB", "Open Closed field-line Boundary"), + bname.upper(), bound_desc[bname]), pysat_inst.meta.labels.min_val: -np.inf, pysat_inst.meta.labels.max_val: np.inf} elif isvector: - name = ("OCB_" + ocb_name.split("_ocb")[0]).replace("_", " ") + name = (bextra + ocb_name.split("_{:s}".format(bname))[0]).replace( + "_", " ") new_meta = {pysat_inst.meta.labels.fill_val: np.nan, pysat_inst.meta.labels.name: name, pysat_inst.meta.labels.desc: "".join([ - "Open Closed field-line Boundary vector ", - pysat_inst.meta[pysat_name][ + bound_desc[bname], pysat_inst.meta[pysat_name][ pysat_inst.meta.labels.desc]]), pysat_inst.meta.labels.units: pysat_inst.meta[ pysat_name][pysat_inst.meta.labels.units], @@ -642,10 +706,9 @@ def add_ocb_to_metadata(pysat_inst, ocb_name, pysat_name, overwrite=False, # Update certain categories with OCB information new_meta[pysat_inst.meta.labels.fill_val] = np.nan new_meta[pysat_inst.meta.labels.name] = "".join([ - "OCB ", new_meta[pysat_inst.meta.labels.name]]) + bname.upper(), " ", new_meta[pysat_inst.meta.labels.name]]) new_meta[pysat_inst.meta.labels.desc] = "".join([ - "Open Closed field-line Boundary ", - new_meta[pysat_inst.meta.labels.desc]]) + bound_desc[bname], new_meta[pysat_inst.meta.labels.desc]]) # Set the notes new_meta[pysat_inst.meta.labels.notes] = notes From d6dd92204cc616bff6df5ca9936aa47b2f3b6fda Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 16:53:01 -0400 Subject: [PATCH 43/60] TST: update pysat tests Update pysat tests by: - adding a base class for the pysat methods tests, - setting boundary classes by input kwargs, - updating structures for new inputs, and - using new test file names. --- ocbpy/tests/test_pysat.py | 387 +++++++++++++++++--------------------- 1 file changed, 173 insertions(+), 214 deletions(-) diff --git a/ocbpy/tests/test_pysat.py b/ocbpy/tests/test_pysat.py index 4b65166e..4127825e 100644 --- a/ocbpy/tests/test_pysat.py +++ b/ocbpy/tests/test_pysat.py @@ -43,7 +43,6 @@ def setUp(self): """Initialize the test environment.""" # Set the default function values - self.ocb_key = 'ocb_test' self.pysat_key = 'dummy1' self.pysat_lat = 'latitude' self.pysat_alt = 'altitude' @@ -53,17 +52,22 @@ def setUp(self): self.added_keys = list() self.pysat_keys = list() self.del_time = 600 + self.ocb_name = "ocb" + self.ocb_key = '{:s}_test'.format(self.ocb_name) return def tearDown(self): """Tear down the testing environment.""" del self.ocb_key, self.pysat_key, self.notes, self.del_time del self.test_inst, self.ocb, self.added_keys, self.pysat_keys - del self.pysat_lat, self.pysat_alt + del self.pysat_lat, self.pysat_alt, self.ocb_name return def eval_ocb_metadata(self): """Evaluate new OCB metadata.""" + bound_desc = {"ocb": "Open Closed field-line Boundary", + "eab": "Equatorward Auroral Boundary", + "dualb": "Dual Boundary", "": ""} # Test MetaData exists if self.test_inst is not None: @@ -121,7 +125,7 @@ def eval_ocb_metadata(self): # Test the elements that have "OCB" appended to the text sline = self.test_inst.meta[self.ocb_key][ self.test_inst.meta.labels.name].split(" ") - self.assertRegex(sline[0], "OCB") + self.assertRegex(sline[0], self.ocb_name.upper(), msg=sline) note_line = self.test_inst.meta[self.ocb_key][ self.test_inst.meta.labels.notes] if self.pysat_key is not None and note_line.find( @@ -133,8 +137,8 @@ def eval_ocb_metadata(self): self.pysat_key, note_line)) # Test the remaining elements - self.assertEqual(self.test_inst.meta[self.ocb_key][ - self.test_inst.meta.labels.desc].find("Open Closed"), 0) + self.assertRegex(self.test_inst.meta[self.ocb_key][ + self.test_inst.meta.labels.desc], bound_desc[self.ocb_name]) if self.notes is not None: self.assertRegex(self.test_inst.meta[self.ocb_key][ self.test_inst.meta.labels.notes], self.notes) @@ -205,73 +209,32 @@ def test_ocb_added(self): return -@unittest.skipIf(no_pysat, "pysat not installed") -class TestPysatStructure(unittest.TestCase): - """Unit tests for the pysat instrument functions.""" - - def test_add_ocb_to_data_defaults(self): - """Test the add_ocb_to_data function defaults.""" - defaults = ocb_pysat.add_ocb_to_data.__defaults__ - - for i in [0, 1, 2, 10]: - self.assertEqual(len(defaults[i]), 0, - msg="Default {:d} value {:} != 0".format( - i, defaults[i])) - - for i in [3, 4, 5, 8, 12, 13]: - self.assertIsNone(defaults[i]) - - self.assertEqual(defaults[6], 350) - self.assertEqual(defaults[7], 0) - self.assertRegex(defaults[9], 'default') - self.assertEqual(defaults[11], 60) - self.assertRegex(defaults[14], 'magnetic') - self.assertRegex(defaults[15], 'magnetic') - return - - def test_add_ocb_to_metadata_defaults(self): - """Test the add_ocb_to_metadata function defaults.""" - defaults = ocb_pysat.add_ocb_to_metadata.__defaults__ - - for i in [0, 2]: - self.assertFalse(defaults[i]) - - self.assertEqual(defaults[1], '') - return - - -@unittest.skipIf(no_pysat, "pysat not installed") -class TestPysatMethods(TestPysatUtils, cc.TestLogWarnings): - """Integration tests for using ocbpy on pysat pandas data.""" +class PysatBase(TestPysatUtils): + """Base class for pysat testing.""" def setUp(self): - """Initialize the test class.""" - + """Initialize the base class.""" # Set the util default values TestPysatUtils.setUp(self) - # Set the logging values - cc.TestLogWarnings.setUp(self) - # Set the method default values - self.test_file = path.join(cc.test_dir, "test_north_circle") + self.test_file = path.join(cc.test_dir, "test_north_ocb") self.ocb_kw = {"filename": self.test_file, "instrument": "image", "hemisphere": 1} self.ocb_class = ocbpy.OCBoundary self.rec_ind = 27 self.pysat_var2 = 'dummy2' self.test_module = pysat.instruments.pysat_testing - + self.pysat_kw = {} return def tearDown(self): """Tear down after each test.""" - - cc.TestLogWarnings.tearDown(self) + # Set the util default values TestPysatUtils.tearDown(self) del self.test_file, self.ocb_kw, self.ocb_class, self.rec_ind - del self.pysat_var2, self.test_module + del self.pysat_var2, self.test_module, self.pysat_kw return def load_boundaries(self): @@ -291,11 +254,15 @@ def load_instrument(self): if self.ocb is None: self.load_boundaries() + if 'file_date_range' not in self.pysat_kw: + self.pysat_kw['file_date_range'] = pds.date_range( + self.ocb.dtime[0], self.ocb.dtime[-1], freq='1D') + + if 'num_samples' not in self.pysat_kw: + self.pysat_kw['num_samples'] = 50400 + self.test_inst = pysat.Instrument(inst_module=self.test_module, - num_samples=50400, - file_date_range=pds.date_range( - self.ocb.dtime[0], - self.ocb.dtime[-1], freq='1D')) + **self.pysat_kw) # Reduce pysat warnings # TODO(#130) remove version checking by updating minimum supported pysat @@ -308,17 +275,89 @@ def load_instrument(self): self.test_inst.load(**load_kwargs) return + def set_new_keys(self, exclude_r_corr=True): + """Set the `added_keys` and `pysat_keys` attributes.""" + self.added_keys = [kk for kk in self.test_inst.meta.keys() + if kk.find('_{:s}'.format(self.ocb_name)) > 0] + + if exclude_r_corr: + self.pysat_keys = list() + for aa in self.added_keys: + pp = aa.split("_{:s}".format(self.ocb_name))[0] + if pp == "r_corr": + pp = None + elif pp not in self.test_inst.variables: + pp = self.pysat_key + self.pysat_keys.append(pp) + else: + self.pysat_keys = [aa.split("_{:s}".format(self.ocb_name))[0] + for aa in self.added_keys] + return + + +@unittest.skipIf(no_pysat, "pysat not installed") +class TestPysatStructure(unittest.TestCase): + """Unit tests for the pysat instrument functions.""" + + def test_add_ocb_to_data_defaults(self): + """Test the add_ocb_to_data function defaults.""" + defaults = ocb_pysat.add_ocb_to_data.__defaults__ + + for i in [0, 1, 2, 10]: + self.assertEqual(len(defaults[i]), 0, + msg="Default {:d} value {:} != 0".format( + i, defaults[i])) + + for i in [3, 4, 5, 8, 12, 13]: + self.assertIsNone(defaults[i]) + + self.assertEqual(defaults[6], 350) + self.assertEqual(defaults[7], 0) + self.assertRegex(defaults[9], 'ocb') + self.assertEqual(defaults[11], 60) + self.assertRegex(defaults[14], 'magnetic') + self.assertRegex(defaults[15], 'magnetic') + return + + def test_add_ocb_to_metadata_defaults(self): + """Test the add_ocb_to_metadata function defaults.""" + defaults = ocb_pysat.add_ocb_to_metadata.__defaults__ + + for i in [0, 2]: + self.assertFalse(defaults[i]) + + self.assertEqual(defaults[1], '') + return + + +@unittest.skipIf(no_pysat, "pysat not installed") +class TestPysatMethods(cc.TestLogWarnings, PysatBase): + """Integration tests for using ocbpy on pysat pandas data.""" + + def setUp(self): + """Initialize the test class.""" + PysatBase.setUp(self) + cc.TestLogWarnings.setUp(self) + return + + def tearDown(self): + """Tear down after each test.""" + cc.TestLogWarnings.tearDown(self) + PysatBase.tearDown(self) + return + def test_add_ocb_to_metadata(self): """Test the metadata adding routine.""" # Load the data and boundaries self.load_instrument() # Test the metadata operation - ocb_pysat.add_ocb_to_metadata(self.test_inst, "ocb_test", + ocb_pysat.add_ocb_to_metadata(self.test_inst, self.ocb_key, self.pysat_key, notes="test notes") self.notes = 'test notes' self.eval_ocb_metadata() + return def test_add_ocb_to_metadata_vector(self): """Test the metadata adding routine for vector data.""" @@ -326,13 +365,14 @@ def test_add_ocb_to_metadata_vector(self): self.load_instrument() # Test the metadata operation - ocb_pysat.add_ocb_to_metadata(self.test_inst, "ocb_test", + ocb_pysat.add_ocb_to_metadata(self.test_inst, self.ocb_key, self.pysat_key, notes="test notes scaled using None", isvector=True) self.notes = 'test notes scaled using None' self.eval_ocb_metadata() + return def test_no_overwrite_metadata(self): """Test the overwrite block on metadata adding routine.""" @@ -341,11 +381,11 @@ def test_no_overwrite_metadata(self): # Test the metadata overwriting failure self.test_add_ocb_to_metadata() - ocb_pysat.add_ocb_to_metadata(self.test_inst, "ocb_test", + ocb_pysat.add_ocb_to_metadata(self.test_inst, self.ocb_key, self.pysat_key, notes="test notes two", overwrite=False) - self.lwarn = u"OCB data already has metadata" + self.lwarn = u"Boundary data already has metadata" self.eval_logging_message() return @@ -356,12 +396,13 @@ def test_overwrite_metadata(self): # Test the metadata overwriting success self.test_add_ocb_to_metadata() - ocb_pysat.add_ocb_to_metadata(self.test_inst, "ocb_test", + ocb_pysat.add_ocb_to_metadata(self.test_inst, self.ocb_key, self.pysat_key, notes="test notes two", overwrite=True) meta = self.test_inst.meta - self.assertRegex(meta['ocb_test'][meta.labels.notes], "test notes two") + self.assertRegex(meta[self.ocb_key][meta.labels.notes], + "test notes two") return def test_add_ocb_to_data_ocb_obj(self): @@ -374,34 +415,27 @@ def test_add_ocb_to_data_ocb_obj(self): height_name=self.pysat_alt, ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = [aa.split("_ocb")[0] for aa in self.added_keys] + self.set_new_keys(exclude_r_corr=False) self.assertIn('r_corr', self.pysat_keys) self.pysat_keys[self.pysat_keys.index("r_corr")] = None self.test_ocb_added() return def test_add_ocb_to_data_ocb_file(self): - """Test adding ocb to pysat data using the OCB file name.""" + """Test adding ocb to pysat data using one boundary file name.""" # Load the data and boundaries self.load_instrument() - - # This test will not work for Dual-Boundary objects - if isinstance(self.ocb, ocbpy._boundary.DualBoundary): - self.skipTest("".join(["`pysat_instruments.add_ocb_to_data` ", - "requires object initialization for ", - "DualBoundary calculations."])) + if 'filename' in self.ocb_kw.keys(): + self.ocb_kw['ocbfile'] = self.ocb_kw['filename'] + else: + self.ocb_kw['ocbfile'] = 'dual' # Test adding OCBs using filename instead of OCB object - self.ocb_kw['ocbfile'] = self.ocb_kw['filename'] ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", height_name=self.pysat_alt, max_sdiff=self.del_time, **self.ocb_kw) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = [aa.split("_ocb")[0] for aa in self.added_keys] + self.set_new_keys(exclude_r_corr=False) self.assertIn('r_corr', self.pysat_keys) self.pysat_keys[self.pysat_keys.index("r_corr")] = None self.test_ocb_added() @@ -411,6 +445,10 @@ def test_add_ocb_to_data_southern_hemisphere(self): """Test successful identification of southern hemisphere only.""" # Load the data and boundaries self.load_instrument() + if 'filename' in self.ocb_kw.keys(): + self.ocb_kw['ocbfile'] = self.ocb_kw['filename'] + else: + self.ocb_kw['ocbfile'] = 'dual' # Don't set the hemisphere del self.ocb_kw['hemisphere'] @@ -449,9 +487,7 @@ def test_add_ocb_to_data_evar(self): evar_names=[self.pysat_key], ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = [aa.split("_ocb")[0] for aa in self.added_keys] + self.set_new_keys(exclude_r_corr=False) self.assertIn('r_corr', self.pysat_keys) self.pysat_keys[self.pysat_keys.index("r_corr")] = None @@ -469,9 +505,7 @@ def test_add_ocb_to_data_curl_evar(self): curl_evar_names=[self.pysat_var2], ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = [aa.split("_ocb")[0] for aa in self.added_keys] + self.set_new_keys(exclude_r_corr=False) self.assertIn('r_corr', self.pysat_keys) self.pysat_keys[self.pysat_keys.index("r_corr")] = None @@ -495,17 +529,7 @@ def test_add_ocb_to_data_evar_vect(self): 'dat_units': 'm/s'}}, ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = list() - for aa in self.added_keys: - pp = aa.split("_ocb")[0] - if pp == "r_corr": - pp = None - elif pp not in self.test_inst.variables: - pp = self.pysat_key - self.pysat_keys.append(pp) - + self.set_new_keys(exclude_r_corr=True) self.test_ocb_added() return @@ -526,17 +550,7 @@ def test_add_ocb_to_data_curl_evar_vect(self): 'dat_units': 'm/s'}}, ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = list() - for aa in self.added_keys: - pp = aa.split("_ocb")[0] - if pp == "r_corr": - pp = None - elif pp not in self.test_inst.variables: - pp = self.pysat_key - self.pysat_keys.append(pp) - + self.set_new_keys(exclude_r_corr=True) self.test_ocb_added() return @@ -556,16 +570,7 @@ def test_add_ocb_to_data_custom_vect(self): 'scale_func': None}}, ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = list() - for aa in self.added_keys: - pp = aa.split("_ocb")[0] - if pp == "r_corr": - pp = None - elif pp not in self.test_inst.variables: - pp = self.pysat_key - self.pysat_keys.append(pp) + self.set_new_keys(exclude_r_corr=True) self.test_ocb_added() return @@ -588,17 +593,7 @@ def test_add_ocb_to_data_all_types(self): 'scale_func': None}}, ocb=self.ocb, max_sdiff=self.del_time) - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = list() - for aa in self.added_keys: - pp = aa.split("_ocb")[0] - if pp == "r_corr": - pp = None - elif pp not in self.test_inst.variables: - pp = self.pysat_key - self.pysat_keys.append(pp) - + self.set_new_keys(exclude_r_corr=True) self.test_ocb_added() return @@ -609,12 +604,12 @@ def test_add_ocb_to_data_no_file(self): # Add the OCB without a filename self.ocb_kw['ocbfile'] = None - ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", - height_name=self.pysat_alt, **self.ocb_kw, - max_sdiff=self.del_time) - self.lwarn = u"no data in Boundary file" - self.eval_logging_message() + with self.assertRaisesRegex(ValueError, + "can't determine desired boundary type"): + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, **self.ocb_kw, + max_sdiff=self.del_time) return def test_add_ocb_to_data_bad_hemisphere_selfset(self): @@ -636,8 +631,10 @@ def test_bad_pysat_inst(self): self.load_instrument() # Set the function and input data - func_dict = {ocb_pysat.add_ocb_to_data: [None, self.pysat_lat, "mlt"], - ocb_pysat.add_ocb_to_metadata: [None, "ocb_mlt", "mlt"]} + func_dict = { + ocb_pysat.add_ocb_to_data: [None, self.pysat_lat, "mlt"], + ocb_pysat.add_ocb_to_metadata: [ + None, "{:s}_mlt".format(self.ocb_name), "mlt"]} # Test the error for each function for func in func_dict.keys(): @@ -740,6 +737,8 @@ def setUp(self): self.ocb_kw = {"filename": self.test_file, "instrument": "image", "hemisphere": 1} self.ocb_class = ocbpy.EABoundary + self.ocb_name = "eab" + self.ocb_key = "_".join([self.ocb_name, "test"]) return @@ -755,10 +754,11 @@ def setUp(self): # Update the method defaults self.ocb_class = ocbpy.DualBoundary + self.ocb_name = "dualb" + self.ocb_key = "_".join([self.ocb_name, "test"]) self.ocb_kw = {'ocb_filename': self.test_file, 'ocb_instrument': 'image', - 'eab_filename': self.test_file.replace('north_circle', - 'north_eab'), + 'eab_filename': self.test_file.replace('_ocb', '_eab'), 'eab_instrument': 'image', 'hemisphere': 1} self.rec_ind = 0 @@ -819,77 +819,27 @@ def test_mismatched_vector_data(self): @unittest.skipIf(no_pysat, "pysat not installed") -class TestPysatCustMethods(TestPysatUtils, cc.TestLogWarnings): +class TestPysatCustMethods(PysatBase): """Integration tests for using ocbpy as a custom function with pysat pandas. """ def setUp(self): """Initialize the unit tests for using the pysat.Custom methods.""" + PysatBase.setUp(self) - # Set the util default values - TestPysatUtils.setUp(self) - - # Set the logging values - cc.TestLogWarnings.setUp(self) - - # Set the method defaults - self.test_file = path.join(cc.test_dir, "test_north_circle") - self.ocb_kw = {"filename": self.test_file, "instrument": "image", - "hemisphere": 1} - self.ocb_class = ocbpy.OCBoundary - self.rec_ind = 27 - self.test_module = pysat.instruments.pysat_testing - self.pysat_var2 = 'dummy2' + # Set the custom defaults self.cust_kwargs = {'mlat_name': self.pysat_lat, 'mlt_name': 'mlt', 'height_name': self.pysat_alt, 'max_sdiff': self.del_time} - self.pysat_kw = {} return def tearDown(self): """Clean the test environment.""" + PysatBase.tearDown(self) - cc.TestLogWarnings.tearDown(self) - TestPysatUtils.tearDown(self) - - del self.test_file, self.ocb_kw, self.ocb_class, self.pysat_kw - del self.pysat_var2, self.test_module, self.cust_kwargs - return - - def load_boundaries(self): - """Load the OCB boundary class object for testing.""" - # Verify the existence of the test file - self.assertTrue(path.isfile(self.test_file), - msg="'{:}' is not a file".format(self.test_file)) - - # Set up the OCB object - self.ocb = self.ocb_class(**self.ocb_kw) - if self.rec_ind < self.ocb.records: - self.ocb.rec_ind = self.rec_ind - return - - def load_instrument(self): - """Load the pysat Instrument for testing.""" - if self.ocb is None: - self.load_boundaries() - - self.pysat_kw['file_date_range'] = pds.date_range( - self.ocb.dtime[0], self.ocb.dtime[-1], freq='1D') - - self.test_inst = pysat.Instrument(inst_module=self.test_module, - num_samples=50400, **self.pysat_kw) - - # Reduce pysat warnings - # TODO(#130) remove version checking by updating minimum supported pysat - load_kwargs = {'date': self.ocb.dtime[self.rec_ind]} - if version.Version(pysat.__version__) > version.Version( - '3.0.1') and version.Version( - pysat.__version__) < version.Version('3.2.0'): - load_kwargs['use_header'] = True - - self.test_inst.load(**load_kwargs) + del self.cust_kwargs return def test_load(self): @@ -909,10 +859,18 @@ def test_cust_add_ocb_to_data(self): # Load the boundaries self.load_boundaries() + # Set the second input set by type + if self.ocb_name == "dualb": + kw2 = ['ocbfile', 'instrument', 'hemisphere', 'ocb_filename', + 'eab_filename'] + val2 = ['dual', 'image', 1, self.test_file, + self.ocb_kw['eab_filename']] + else: + kw2 = ['ocbfile', 'instrument', 'hemisphere'] + val2 = [self.test_file, 'image', 1] + # Cycle through the different custom inputs - for kw, val in [(['ocb'], [self.ocb]), - (['ocbfile', 'instrument', 'hemisphere'], - [self.test_file, 'image', 1]), + for kw, val in [(['ocb'], [self.ocb]), (kw2, val2), (['ocb', 'evar_names'], [self.ocb, [self.pysat_key]]), (['ocb', 'curl_evar_names'], [self.ocb, [self.pysat_var2]])]: @@ -932,11 +890,10 @@ def test_cust_add_ocb_to_data(self): self.test_load() # Test the additional outputs - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = [aa.split("_ocb")[0] - for aa in self.added_keys] - self.assertIn('r_corr', self.pysat_keys) + self.set_new_keys(exclude_r_corr=False) + self.assertIn('r_corr', self.pysat_keys, + msg="r_corr missing from {:}".format( + self.test_inst.meta)) self.pysat_keys[self.pysat_keys.index("r_corr")] = None self.test_ocb_added() return @@ -980,6 +937,7 @@ def test_cust_add_ocb_to_data_vect(self): # Update the Instrument to include a custom function self.pysat_kw['custom'] = [{'function': ocb_pysat.add_ocb_to_data, 'kwargs': dict(self.cust_kwargs)}] + # Update the custom function kwargs for i, kw_val in enumerate(kw): self.pysat_kw['custom'][0]['kwargs'][kw_val] = val[i] @@ -989,16 +947,7 @@ def test_cust_add_ocb_to_data_vect(self): self.test_load() # Test the additional data - self.added_keys = [kk for kk in self.test_inst.meta.keys() - if kk.find('_ocb') > 0] - self.pysat_keys = list() - for aa in self.added_keys: - pp = aa.split("_ocb")[0] - if pp == "r_corr": - pp = None - elif pp not in self.test_inst.variables: - pp = self.pysat_key - self.pysat_keys.append(pp) + self.set_new_keys(exclude_r_corr=True) self.test_ocb_added() return @@ -1013,12 +962,11 @@ def test_cust_add_ocb_to_data_no_file(self): self.pysat_kw['custom'] = [{'function': ocb_pysat.add_ocb_to_data, 'kwargs': self.cust_kwargs}] - # Test the defaults - self.test_load() + # Test the correct error is raised + with self.assertRaisesRegex(ValueError, + "can't determine desired boundary type"): + self.test_load() - # Test the logging output - self.lwarn = u'no data in Boundary file' - self.eval_logging_message() return def test_cust_add_ocb_to_data_bad_inputs(self): @@ -1041,10 +989,12 @@ def test_cust_add_ocb_to_data_bad_inputs(self): def test_cust_add_ocb_to_data_bad_vector_scale(self): """Test failure of missing scaling function in custom func.""" + self.load_boundaries() self.cust_kwargs['vector_names'] = {'bad': {'vect_n': 'bad_n', 'vect_e': 'bad_e', 'dat_name': 'bad', 'dat_units': ''}} + self.cust_kwargs['ocb'] = self.ocb self.pysat_kw['custom'] = [{'function': ocb_pysat.add_ocb_to_data, 'kwargs': self.cust_kwargs}] @@ -1055,6 +1005,8 @@ def test_cust_add_ocb_to_data_bad_vector_scale(self): def test_cust_add_ocb_to_data_bad_vector_name(self): """Test failure of missing scaling function in custom func.""" + self.load_boundaries() + self.cust_kwargs['ocb'] = self.ocb self.cust_kwargs['evar_names'] = ['bad'] self.cust_kwargs['vector_names'] = {'bad': {'vect_n': 'bad_n', @@ -1079,7 +1031,12 @@ def setUp(self): super().setUp() # Update the class defaults + self.test_file = path.join(cc.test_dir, "test_north_eab") + self.ocb_kw = {"filename": self.test_file, + "instrument": "image", "hemisphere": 1} self.ocb_class = ocbpy.EABoundary + self.ocb_name = 'eab' + self.ocb_key = "_".join([self.ocb_name, "test"]) return @@ -1096,9 +1053,11 @@ def setUp(self): # Update the class defaults self.ocb_class = ocbpy.DualBoundary + self.ocb_name = 'dualb' + self.ocb_key = "_".join([self.ocb_name, "test"]) self.ocb_kw = {'ocb_filename': self.test_file, 'ocb_instrument': 'image', - 'eab_filename': self.test_file.replace('north_circle', + 'eab_filename': self.test_file.replace('north_ocb', 'north_eab'), 'eab_instrument': 'image', 'hemisphere': 1} self.rec_ind = 0 From 862879ea3789c7b973db608c7c6673c67c71aa17 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 16:53:24 -0400 Subject: [PATCH 44/60] DOC: improve error message Improve the error message output to be more informative. --- ocbpy/_boundary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocbpy/_boundary.py b/ocbpy/_boundary.py index b5ca1b80..91f5d8a6 100644 --- a/ocbpy/_boundary.py +++ b/ocbpy/_boundary.py @@ -861,7 +861,7 @@ def _set_default_rfunc(self): self.rfunc = np.full(shape=self.records, fill_value=ocbcor.elliptical) else: - raise ValueError("unknown instrument") + raise ValueError("unknown instrument: {:}".format(self.instrument)) return From e5bc7a2b174a02525162d813e1543a8319ff0fa2 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 17:08:16 -0400 Subject: [PATCH 45/60] MAINT: cycled Python versions Updated the supported Python versions to reflect current NEP 29. --- .github/workflows/main.yml | 2 +- README.md | 2 +- pyproject.toml | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f8f7fdf..56cb7add 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "macos-latest", "windows-latest"] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] install-extras: [0, 1, 2] # TODO(#129): update to replace extra flag exclude: - os: "windows-latest" diff --git a/README.md b/README.md index 61f0f7e7..8a7b6481 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ These routines may be used as a guide to write routines for other datasets. # Python versions -This module currently supports Python version 3.7 - 3.10. +This module currently supports Python version 3.9 - 3.12. # Dependencies diff --git a/pyproject.toml b/pyproject.toml index 45600209..0007e347 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,11 +45,10 @@ classifiers = [ "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", 'Operating System :: Unix', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', From 32d47dbfdd8c880922160b0bde30ceee35a348b6 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 6 May 2024 17:09:01 -0400 Subject: [PATCH 46/60] DOC: updated changelog Added the Python cycling to the changelog. --- Changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.rst b/Changelog.rst index 907fd922..fa49aea0 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -7,6 +7,7 @@ Summary of all changes made since the first stable release ------------------ * DEP: Deprecated functions that depend on ssj_auroral_boundary_package * DEP: Removed classes and kwargs deprecated in v0.3.0 +* MAINT: Cycled supported Python versions * MAINT: Added a pyproject.toml file and removed setup.py * MAINT: Updated numpy logic to address DeprecationWarnings * MAINT: Updated GitHub Action yamls to use pyproject.toml and cycle versions From 1ed4a1ddf3fe85975c2849776c0df0cc760ced9a Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 09:53:14 -0400 Subject: [PATCH 47/60] TST: exclude failing extra tests Remove failing tests that rely on deprecated code. --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 56cb7add..44876cd6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,9 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12"] install-extras: [0, 1, 2] # TODO(#129): update to replace extra flag exclude: - - os: "windows-latest" + - os: "windows-latest" # TODO(#135): remove exclusion + install-extras: 2 + - os: "macos-latest" # TODO(#135): remove exclusion install-extras: 2 name: Python ${{ matrix.python-version }} on ${{ matrix.os }} with extras ${{ matrix.install-extras }} From 4af67ad47f89752c7ec16cafb862b6023b0eb2d3 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 12:01:37 -0400 Subject: [PATCH 48/60] BUG: improved mixed inputs Use time conversion function to determine the desired range and improved the handling of mixed inputs to `calc_dest_polar_angle`. --- ocbpy/vectors.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index a28212a1..c7a101cb 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -71,14 +71,10 @@ def calc_vec_pole_angle(data_lt, data_lat, pole_lt, pole_lat): """ # Convert the local time values to radians, after calculating the - # difference between the destination pole and the data. - del_long = ocb_time.hr2rad(np.asarray(pole_lt) - np.asarray(data_lt)) - - if len(del_long.shape) == 0: - if del_long < -np.pi: - del_long += 2.0 * np.pi - else: - del_long[del_long < -np.pi] += 2.0 * np.pi + # difference between the destination pole and the data. Restrict data + # from -pi to pi + del_long = ocb_time.hr2rad(np.asarray(pole_lt) - np.asarray(data_lt), + max_range=np.pi) # Initalize the output pole_angle = np.full(shape=del_long.shape, fill_value=np.nan) @@ -327,10 +323,8 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): base_naz_angle = np.asarray(base_naz_angle) pole_angle = np.asarray(pole_angle) - # Initialise the output and set the quadrant dictionary + # Initialise the quadrant dictionary nan_mask = ~np.isnan(base_naz_angle) & ~np.isnan(pole_angle) - dest_naz_angle = np.full(shape=(base_naz_angle + pole_angle).shape, - fill_value=np.nan) quads = {oquad: {vquad: (pole_quad == oquad) & (vect_quad == vquad) & nan_mask for vquad in quad_range} for oquad in quad_range} @@ -343,10 +337,16 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): maa_mask = quads[3][2] | quads[4][1] cir_mask = quads[3][4] | quads[4][3] + # Initialise the output + dest_naz_angle = np.full(shape=(base_naz_angle + pole_angle + + abs_mask).shape, fill_value=np.nan) + # Calculate OCB polar angle based on the quadrants and other angles if np.any(abs_mask): if len(dest_naz_angle.shape) == 0: dest_naz_angle = abs(base_naz_angle - pole_angle) + elif len(nan_mask.shape) == 0: + dest_naz_angle[abs_mask] = abs(base_naz_angle - pole_angle) else: dest_naz_angle[abs_mask] = abs(base_naz_angle - pole_angle)[abs_mask] @@ -356,6 +356,11 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): dest_naz_angle = pole_angle + base_naz_angle if dest_naz_angle > 180.0: dest_naz_angle = 360.0 - dest_naz_angle + elif len(nan_mask.shape) == 0: + add_val = pole_angle + base_naz_angle + dest_naz_angle[add_mask] = add_val + if add_val > 180.0: + dest_naz_angle[add_mask] = 360.0 - add_val else: dest_naz_angle[add_mask] = (pole_angle + base_naz_angle)[add_mask] lmask = (dest_naz_angle > 180.0) & add_mask @@ -365,18 +370,24 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): if np.any(mpa_mask): if len(dest_naz_angle.shape) == 0: dest_naz_angle = base_naz_angle - pole_angle + elif len(nan_mask.shape) == 0: + dest_naz_angle[mpa_mask] = (base_naz_angle - pole_angle) else: dest_naz_angle[mpa_mask] = (base_naz_angle - pole_angle)[mpa_mask] if np.any(maa_mask): if len(dest_naz_angle.shape) == 0: dest_naz_angle = pole_angle - base_naz_angle + elif len(nan_mask.shape) == 0: + dest_naz_angle[maa_mask] = (pole_angle - base_naz_angle) else: dest_naz_angle[maa_mask] = (pole_angle - base_naz_angle)[maa_mask] if np.any(cir_mask): if len(dest_naz_angle.shape) == 0: dest_naz_angle = 360.0 - base_naz_angle - pole_angle + elif len(nan_mask.shape) == 0: + dest_naz_angle[cir_mask] = 360.0 - base_naz_angle - pole_angle else: dest_naz_angle[cir_mask] = (360.0 - base_naz_angle - pole_angle)[cir_mask] From 9b49338f66799cb1fda901482cc2c313d3beea37 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 12:02:09 -0400 Subject: [PATCH 49/60] TST: improved vector coverage Added tests to access lines uncovered in the vector subroutines. --- ocbpy/tests/test_vectors.py | 55 +++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/ocbpy/tests/test_vectors.py b/ocbpy/tests/test_vectors.py index 3e8439e3..fff5c181 100644 --- a/ocbpy/tests/test_vectors.py +++ b/ocbpy/tests/test_vectors.py @@ -298,26 +298,40 @@ def test_define_vect_quadrants_mixed(self): def test_calc_dest_polar_angle_float(self): """Test the north azimuth angle calculation for float inputs.""" # Set the expected pole quadrant output - self.comp = {1: {1: 138.11957692472973, 2: 148.11957692472973, - 3: 148.11957692472973, 4: -138.11957692472973}, - 2: {1: 148.11957692472973, 2: 138.11957692472973, - 3: -138.11957692472973, 4: 148.11957692472973}, - 3: {1: 148.11957692472973, 2: 138.11957692472973, - 3: 138.11957692472973, 4: 211.88042307527027}, - 4: {1: 138.11957692472973, 2: 148.11957692472973, - 3: 211.88042307527027, 4: 138.11957692472973}} + self.comp = {1: {1: [138.11957692472973, 132.0], + 2: [148.11957692472973, -132.0], + 3: [148.11957692472973, -132.0], + 4: [-138.11957692472973, -132.0]}, + 2: {1: [148.11957692472973, -132.0], + 2: [138.11957692472973, 132.0], + 3: [-138.11957692472973, -132.0], + 4: [148.11957692472973, -132.0]}, + 3: {1: [148.11957692472973, -132.0], + 2: [138.11957692472973, 132.0], + 3: [138.11957692472973, 132.0], + 4: [211.88042307527027, -132.0]}, + 4: {1: [138.11957692472973, 132.0], + 2: [148.11957692472973, -132.0], + 3: [211.88042307527027, -132.0], + 4: [138.11957692472973, 132.0]}} # Cycle through each of the pole quadrants for pole_quad in np.arange(1, 5, 1): # Cycle through each of the vector quadrants for vect_quad in np.arange(1, 5, 1): - with self.subTest(pole_quad=pole_quad, vect_quad=vect_quad): - # Get the output - self.out = vectors.calc_dest_polar_angle( - pole_quad, vect_quad, 5.0, self.pole_ang[-1]) + # Cycle through small and big angles + for i, ang_args in enumerate([(5.0, self.pole_ang[-1]), + (180.0, 312.0)]): + args = [pole_quad, vect_quad, ang_args[0], ang_args[1]] + with self.subTest(args=args): + # Get the output + self.out = vectors.calc_dest_polar_angle(*args) - # Test the integer quadrant assignment - self.assertEqual(self.out, self.comp[pole_quad][vect_quad]) + # Test the integer quadrant assignment + self.assertEqual(self.out, + self.comp[pole_quad][vect_quad][i], + msg="unexpected output: {:}".format( + self.out)) return def test_calc_dest_polar_angle_array(self): @@ -361,7 +375,9 @@ def test_calc_dest_polar_angle_mixed(self): ([[1, 2], [3, 4], 5.0, [91.720246, 143.11957]], np.array([96.720246, 148.11957])), ([[3, 4], [2, 1], [0.0, 90.0], 10.0], - np.array([10.0, -80.0]))]: + np.array([10.0, -80.0])), + ([[1, 1, 2, 3, 4], [1, 2, 3, 4, 1], 180, 312], + np.array([132, -132, -132, -132, 132]))]: with self.subTest(args=args): # Get the output self.out = vectors.calc_dest_polar_angle(*args) @@ -550,12 +566,15 @@ def test_adjust_vector_float(self): def test_adjust_vector_array(self): """Test the vector adjustment with array-like inputs.""" - self.comp = (np.array([2.0, 0.0, -1.0, -1.0896077, -4.75018438, 0.0, + self.comp = (np.array([-2.0, 0.0, -1.0, -1.0896077, -4.75018438, 0.0, 1.41421332, 0.19974281]), - np.array([3.0, 1.0, 3.5, -2.96862848, -1.29836371, 0.0, + np.array([-3.0, 1.0, 3.5, -2.96862848, -1.29836371, 0.0, 0.000820721509, -1.40003672]), np.array([1, 1, 1, 1, 1, 1, 1, 1])) + # Adjust the inputs to cover all lines accessable by array-like input + self.lat[0] = self.pole_lat[0] + 30.0 + # Cycle through list-like or array-like inputs for is_array in [True, False]: # Set the function arguements @@ -601,7 +620,7 @@ def test_adjust_vector_mixed(self): # Cycle through compinations of float/array inputs for ipos, ival, islice in [(0, 0, [0, -1]), (1, 0, [0, 1, 2]), - (2, 1, [1, 5]), (3, 1, [1, -1]), + (2, 2, [2, 7]), (3, 1, [1, -1]), (4, 0, slice(None)), (5, 0, [0, 1, 5]), (6, 0, [0, 6]), (7, 5, [5, 6]), (8, 0, [0, 1, 2]), (9, 3, [3, 5])]: From e34f7995ff9ea68c870e79c2091ec7b00f77120b Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 12:03:33 -0400 Subject: [PATCH 50/60] MAINT: remove support for older Python Remove the support for Python versions less than 3.9. --- ocbpy/__init__.py | 6 +----- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ocbpy/__init__.py b/ocbpy/__init__.py index e6765723..f751612a 100644 --- a/ocbpy/__init__.py +++ b/ocbpy/__init__.py @@ -6,11 +6,7 @@ """Auroral oval and polar cap normalised location calculation tools.""" import logging - -try: - from importlib import metadata -except ImportError: - import importlib_metadata as metadata +from importlib import metadata # Define a logger object to allow easier log handling logging.raiseExceptions = False diff --git a/pyproject.toml b/pyproject.toml index 0007e347..5cd7c632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ description = 'Location relative to open/closed field line boundary' maintainers = [ {name = "Angeline G. Burrell", email = "angeline.g.burrell.civ@us.navy.mil"}, ] -requires-python = ">=3.7" +requires-python = ">=3.9" dependencies = [ "aacgmv2", "numpy", From 32ee07c86ab76c15576cf036b2b19b734d03c58f Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 14:12:30 -0400 Subject: [PATCH 51/60] BUG: fixed `update_loc_coords` Fixed bugs in `update_loc_coords` and elsewhere by: - updated unused kwarg warning to work with or without deprecated kwargs, - added specification of trace method in all applicable methods, - fixed typo that assigned local time data to latitude, and - fixed unnecessary usage of array function. --- ocbpy/ocb_scaling.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index 3a5f949d..d6bc4747 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -158,9 +158,9 @@ def __init__(self, dat_ind, ocb_ind, lat, lt, height=350.0, set_mag = False # Raise a warning - if len(used_dep) == 0: + if len(used_dep) < len(kwargs.keys()): ocbpy.logger.warning('unknown kwargs, ignored: {:}'.format( - kwargs)) + [key for key in kwargs.keys() if key not in used_dep])) else: new_kwargs = [dep_pairs[dep_key] for dep_key in used_dep] warnings.warn("".join(['kwargs have been replaced with new ', @@ -748,7 +748,7 @@ def clear_data(self): warnings.resetwarnings() return - def set_ocb(self, ocb, scale_func=None): + def set_ocb(self, ocb, scale_func=None, trace_method='ALLOWTRACE'): """Set the OCBoundary values for provided data (updates all attributes). Parameters @@ -760,6 +760,8 @@ def set_ocb(self, ocb, scale_func=None): measurement value, measurement latitude (degrees), and measurement boundary-adjusted latitude (degrees). Not necessary if defined earlier or no scaling is needed. (default=None) + trace_method : str + Desired AAGCM tracing method (default='ALLOWTRACE') """ # Update the data values to be in magnetic coordinates @@ -780,7 +782,8 @@ def set_ocb(self, ocb, scale_func=None): # Calculate the coordinates and save the output out_coord = ocb.normal_coord(self.lat, self.lt, coords=self.loc_coord, - height=self.height) + height=self.height, + method=trace_method) self._assign_normal_coord_output(out_coord) else: # Cycle through the boundary indices @@ -794,7 +797,8 @@ def set_ocb(self, ocb, scale_func=None): else: out_coord = ocb.normal_coord(self.lat, self.lt, coords=self.loc_coord, - height=self.height) + height=self.height, + method=trace_method) self._assign_normal_coord_output(out_coord, i) # Exit if the OCB coordinates can't be calculated at this location @@ -1066,7 +1070,8 @@ def calc_vec_pole_angle(self): # When defining vector-pole angles, we will need the vector location in # magnetic coordinates if self.loc_coord != "magnetic": - raise ValueError('need magnetic coordinates to define quadrants') + raise ValueError( + 'need magnetic coordinates to define vector-pole angles') # Cast inputs as arrays self.lt = np.asarray(self.lt) @@ -1094,7 +1099,8 @@ def calc_vec_pole_angle(self): return - def update_loc_coords(self, dtimes, coord='magnetic'): + def update_loc_coords(self, dtimes, coord='magnetic', + trace_method='ALLOWTRACE'): """Update location coordiantes to the desired system. Parameters @@ -1104,6 +1110,8 @@ def update_loc_coords(self, dtimes, coord='magnetic'): coord : str Desired coordinate system, accepts 'magnetic', 'geodetic', and 'geocentric' (default='magnetic') + trace_method : str + Desired AAGCM tracing method (default='ALLOWTRACE') Raises ------ @@ -1127,6 +1135,8 @@ def update_loc_coords(self, dtimes, coord='magnetic'): # There are multiple times and one location self.lt = np.full(shape=len(dtimes), fill_value=self.lt) self.lat = np.full(shape=len(dtimes), fill_value=self.lat) + self.height = np.full(shape=len(dtimes), + fill_value=self.height) else: if hasattr(dtimes, 'year'): # There is one time and multiple locations @@ -1139,7 +1149,7 @@ def update_loc_coords(self, dtimes, coord='magnetic'): raise ValueError('mismatched time and location inputs') # Initalize the AACGM method using the recommending tracing - methods = ["ALLOWTRACE"] + methods = [trace_method] # Handle the conversion to/from magnetic coordinates separately if coord.lower() == "magnetic": @@ -1157,12 +1167,12 @@ def update_loc_coords(self, dtimes, coord='magnetic'): lon = ocb_time.slt2glon(self.lt[i], val) # Convert to magnetic coordinates - out = aacgmv2.get_aacgm_coord_arr( + out = aacgmv2.get_aacgm_coord( self.lat[i], lon, self.height[i], val, method) # Save the output new_lat.append(out[0]) - new_lat.append(out[2]) + new_lt.append(out[2]) else: # Get the longitude lon = ocb_time.slt2glon(self.lt, dtime) @@ -1185,7 +1195,7 @@ def update_loc_coords(self, dtimes, coord='magnetic'): lon = aacgmv2.convert_mlt(self.lt[i], val, m2a=True) # Convert latitude and longitude - out = aacgmv2.convert_latlon_arr( + out = aacgmv2.convert_latlon( self.lat[i], lon, self.height[i], val, method) # Convert to SLT and save the latitude @@ -1209,7 +1219,8 @@ def update_loc_coords(self, dtimes, coord='magnetic'): return - def update_vect_coords_to_mag(self, dtimes, hemisphere): + def update_vect_coords_to_mag(self, dtimes, hemisphere, + trace_method='ALLOWTRACE'): """Convert geographic vector components into AAGGMV2 coordinates. Parameters @@ -1218,6 +1229,8 @@ def update_vect_coords_to_mag(self, dtimes, hemisphere): Datetime or list of datetimes for conversion hemisphere : int -1 for Southern, 1 for Northern + trace_method : str + Desired AAGCM tracing method (default='ALLOWTRACE') Notes ----- @@ -1272,7 +1285,7 @@ def update_vect_coords_to_mag(self, dtimes, hemisphere): dtime = dtimes # Set the AACGM coordinates of the geographic pole - methods = ["ALLOWTRACE"] + methods = [trace_method] if self.vect_coord == "geocentric": methods.append(self.vect_coord.upper()) methods.append("A2G") From faa0164232aab34bee69478dc8b2313aa884f18e Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 14:13:18 -0400 Subject: [PATCH 52/60] TST: added VectorData tests Added vector data tests to: - test the uncovered logging messages, - test the uncovered error messages, and - test `update_loc_coords`. --- ocbpy/tests/test_ocb_scaling.py | 211 ++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index 8b56c0e0..cb9573e8 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -269,6 +269,21 @@ def test_inconsistent_vector_warning(self): self.eval_logging_message() return + def test_unknown_kwargs(self): + """Test warning raised with unknown kwargs.""" + self.lwarn = u"unknown kwargs, ignored:" + + # Initalize the VectorData class with deprecated and unknown kwargs + self.vdata = ocbpy.ocb_scaling.VectorData(0, self.ocb.rec_ind, + 75.0, 22.0, aacgm_n=1.0, + vect_fun=100.0, + dat_name="Test", + dat_units="$m s^{-1}$") + + # Test logging error message for the bad initialization + self.eval_logging_message() + return + class TestOCBScalingMethods(unittest.TestCase): """Unit tests for the VectorData class.""" @@ -796,6 +811,136 @@ def test_unscaled_r(self): self.assertEqual(self.vdata.unscaled_r, 14.09) return + def test_update_loc_coords_float(self): + """Test the location coordinate conversion for floats.""" + mag_out = {"geocentric": [69.6782, 21.41886], + "geodetic": [69.58256, 21.422598]} + + for coord in mag_out.keys(): + self.vdata.loc_coord = coord + + # Ensure the location is reset + self.vdata.lat = 75.0 + self.vdata.lt = 22.0 + + with self.subTest(coord=coord): + # Convert the location + self.vdata.update_loc_coords(self.ocb.dtime[self.ocb.rec_ind]) + + # Evaluate the output + self.assertAlmostEqual( + float(self.vdata.lat), mag_out[coord][0], places=4, + msg="unexpected magnetic latitude") + self.assertAlmostEqual( + float(self.vdata.lt), mag_out[coord][1], places=4, + msg="unexpected MLT") + self.assertRegex(self.vdata.loc_coord, "magnetic") + + # Convert back to geographic coordinates + self.vdata.update_loc_coords(self.ocb.dtime[self.ocb.rec_ind], + coord=coord) + + # Evaluate the output; note the loss of precision + self.assertAlmostEqual(float(self.vdata.lat), 75.0, places=1, + msg="unexpected geographic latitude") + self.assertAlmostEqual(float(self.vdata.lt), 22.0, places=1, + msg="unexpected SLT") + self.assertRegex(self.vdata.loc_coord, coord) + return + + def test_update_loc_coords_array(self): + """Test the location coordinate conversion for arrays.""" + mag_out = {"geocentric": [69.6782, 21.41886], + "geodetic": [69.58256, 21.422598]} + + for coord in mag_out.keys(): + self.vdata.loc_coord = coord + + for is_array in [True, False]: + # Ensure the location is reset + self.vdata.lat = [75.0, 75.0] + self.vdata.lt = [22.0, 22.0] + + # Adjust if list-like input is not desired + if is_array: + self.vdata.lat = numpy.asarray(self.vdata.lat) + self.vdata.lt = numpy.asarray(self.vdata.lt) + + with self.subTest(coord=coord, is_array=is_array): + # Convert the location + self.vdata.update_loc_coords( + self.ocb.dtime[self.ocb.rec_ind]) + + # Evaluate the output + self.assertTrue( + (abs(self.vdata.lat + - mag_out[coord][0]) < 1.0e-3).all(), + msg="unexpected magnetic latitudes: {:} != {:}".format( + self.vdata.lat, mag_out[coord][0])) + self.assertTrue( + (abs(self.vdata.lt - mag_out[coord][1]) < 1.0e-3).all(), + msg="unexpected MLT: {:} != {:}".format( + self.vdata.lt, mag_out[coord][1])) + self.assertRegex(self.vdata.loc_coord, "magnetic") + + # Convert back to geographic coordinates + self.vdata.update_loc_coords( + self.ocb.dtime[self.ocb.rec_ind], coord=coord) + + # Evaluate the output; note the loss of precision + self.assertTrue( + (abs(self.vdata.lat - 75.0) < 0.1).all(), + msg="unexpected geographic latitude") + self.assertTrue( + (abs(self.vdata.lt - 22.0) < 0.1).all(), + msg="unexpected SLT") + self.assertRegex(self.vdata.loc_coord, coord) + return + + def test_update_loc_coords_mult_times(self): + """Test the location coordinate conversion for multiple times.""" + mag_out = {"geocentric": [[69.6782, 69.6881], [21.41886, 21.43416]], + "geodetic": [[69.58256, 69.59329], [21.422598, 21.43755]]} + + # Set multiple boundary indices + rec_inds = [self.ocb.rec_ind] + self.ocb.get_next_good_ocb_ind() + rec_inds.append(self.ocb.rec_ind) + self.vdata.ocb_ind = rec_inds + + for coord in mag_out.keys(): + self.vdata.loc_coord = coord + + # Ensure the location is reset + self.vdata.lat = 75.0 + self.vdata.lt = 22.0 + + with self.subTest(coord=coord): + # Convert the location + self.vdata.update_loc_coords(self.ocb.dtime[self.vdata.ocb_ind]) + + # Evaluate the output + for i, lat in enumerate(self.vdata.lat): + self.assertAlmostEqual(lat, mag_out[coord][0][i], places=4, + msg="unexpected magnetic latitude") + self.assertAlmostEqual( + self.vdata.lt[i], mag_out[coord][1][i], places=4, + msg="unexpected MLT") + self.assertRegex(self.vdata.loc_coord, "magnetic") + + # Convert back to geographic coordinates + self.vdata.update_loc_coords( + self.ocb.dtime[self.vdata.ocb_ind], coord=coord) + + # Evaluate the output; note the loss of precision + for i, lat in enumerate(self.vdata.lat): + self.assertAlmostEqual(lat, 75.0, places=1, + msg="unexpected geographic latitude") + self.assertAlmostEqual(self.vdata.lt[i], 22.0, places=1, + msg="unexpected SLT") + self.assertRegex(self.vdata.loc_coord, coord) + return + class TestDualScalingMethods(TestOCBScalingMethods): """Unit tests for the VectorData class.""" @@ -1175,6 +1320,72 @@ def test_bad_define_quadrants_pole_angle(self): self.vdata.define_quadrants() return + def test_init_with_bad_loc_coord(self): + """Test initialization fails with a bad location coordinate.""" + self.input_attrs = [0, 27, 75.0, 22.0] + self.bad_input = {'vect_n': 100.0, 'vect_e': 100.0, + 'vect_z': 10.0, 'loc_coord': "bad_coords"} + + with self.assertRaisesRegex(ValueError, "unknown location coordinate"): + self.vdata = ocbpy.ocb_scaling.VectorData(*self.input_attrs, + **self.bad_input) + return + + def test_init_with_bad_vect_coord(self): + """Test initialization fails with a bad vector coordinate.""" + self.input_attrs = [0, 27, 75.0, 22.0] + self.bad_input = {'vect_n': 100.0, 'vect_e': 100.0, + 'vect_z': 10.0, 'vect_coord': "bad_coords"} + + with self.assertRaisesRegex(ValueError, "unknown vector coordinate"): + self.vdata = ocbpy.ocb_scaling.VectorData(*self.input_attrs, + **self.bad_input) + return + + def test_init_with_incompatible_coord_combos(self): + """Test initialization fails with a bad coordinate combos.""" + self.input_attrs = [0, 27, 75.0, 22.0] + self.bad_input = {'vect_n': 100.0, 'vect_e': 100.0, + 'vect_z': 10.0, 'vect_coord': "geocentric", + 'loc_coord': 'geodetic'} + + with self.assertRaisesRegex(ValueError, "incompatible vector and loc"): + self.vdata = ocbpy.ocb_scaling.VectorData(*self.input_attrs, + **self.bad_input) + return + + def test_bad_loc_coord_for_quadrants(self): + """Test failure to calculate quadrants without magnetic locations.""" + self.vdata.loc_coord = "geodetic" + + with self.assertRaisesRegex(ValueError, "need magnetic coordinates"): + self.vdata.define_quadrants() + return + + def test_bad_vect_coord_for_quadrants(self): + """Test failure to calculate quadrants without magnetic vectors.""" + self.vdata.vect_coord = "geodetic" + + with self.assertRaisesRegex(ValueError, "need magnetic coordinates"): + self.vdata.define_quadrants() + return + + def test_bad_vector_scaling(self): + """Test failure to scale vectors without locations.""" + self.vdata.lat = numpy.nan + + with self.assertRaisesRegex(ValueError, "Vector locations required"): + self.vdata.scale_vector() + return + + def test_bad_loc_coord_for_vec_pole_angle(self): + """Test failure to calculate vector pole angle without mag locations.""" + self.vdata.loc_coord = "geodetic" + + with self.assertRaisesRegex(ValueError, "need magnetic coordinates"): + self.vdata.calc_vec_pole_angle() + return + class TestHaversine(unittest.TestCase): """Unit tests for the haversine functions.""" From 0425fd7179c59f61d8ef0d3e5d89879b1527539e Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 16:44:20 -0400 Subject: [PATCH 53/60] BUG: fixed `update_vect_coords_to_mag` Fixed missing output and missing use of `trace_method` in `update_vect_coords_to_mag`. --- ocbpy/ocb_scaling.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ocbpy/ocb_scaling.py b/ocbpy/ocb_scaling.py index d6bc4747..ffca1e38 100644 --- a/ocbpy/ocb_scaling.py +++ b/ocbpy/ocb_scaling.py @@ -1247,7 +1247,8 @@ def update_vect_coords_to_mag(self, dtimes, hemisphere, mag_lat = np.asarray(self.lat) # Calculate the geographic location - self.update_loc_coords(dtimes, coord=self.vect_coord) + self.update_loc_coords(dtimes, coord=self.vect_coord, + trace_method=trace_method) geo_lt = np.asarray(self.lt) geo_lat = np.asarray(self.lat) @@ -1261,7 +1262,7 @@ def update_vect_coords_to_mag(self, dtimes, hemisphere, geo_lat = np.asarray(self.lat) # Update the location coordiantes to be magnetic - self.update_loc_coords(dtimes) + self.update_loc_coords(dtimes, trace_method=trace_method) mag_lt = np.asarray(self.lt) mag_lat = np.asarray(self.lat) @@ -1303,7 +1304,7 @@ def update_vect_coords_to_mag(self, dtimes, hemisphere, mag_pole_slt.append(ocb_time.glon2slt(out[1], val)) mag_pole_glat.append(out[0]) else: - mag_pole_glat, mag_pole_lon = aacgmv2.convert_latlon( + mag_pole_glat, mag_pole_lon, _ = aacgmv2.convert_latlon( hemisphere * 90.0, 0.0, self.height, dtime, method_code='|'.join(methods)) mag_pole_slt = ocb_time.glon2slt(mag_pole_lon, dtime) From 695996fb2e8e7429988e74608d663b94c41dec87 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 16:45:48 -0400 Subject: [PATCH 54/60] TST: added `update_vect_coords_to_mag` tests Added unit tests for `update_vect_coords_to_mag`. --- ocbpy/tests/test_ocb_scaling.py | 184 +++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 1 deletion(-) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index cb9573e8..d945620f 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -904,7 +904,10 @@ def test_update_loc_coords_mult_times(self): # Set multiple boundary indices rec_inds = [self.ocb.rec_ind] - self.ocb.get_next_good_ocb_ind() + if hasattr(self.ocb, "get_next_good_ocb_ind"): + self.ocb.get_next_good_ocb_ind() + else: + self.ocb.rec_ind += 1 rec_inds.append(self.ocb.rec_ind) self.vdata.ocb_ind = rec_inds @@ -941,6 +944,185 @@ def test_update_loc_coords_mult_times(self): self.assertRegex(self.vdata.loc_coord, coord) return + def test_update_vect_and_loc_coords_float(self): + """Test the vector and location coordinate conversion for floats.""" + mag_out = {"geocentric": {"lat": 69.6782, "lt": 21.41886, + "geocentric": {"vect_n": 64.00367, + "vect_e": 76.71884, + "vect_z": 5.0}, + "magnetic": {"vect_n": 78.60399, + "vect_e": 61.67384, + "vect_z": 5.0}}, + "geodetic": {"lat": 69.58256, "lt": 21.422598, + "magnetic": {"vect_n": 78.6003, + "vect_e": 61.6786, + "vect_z": 5.0}, + "geodetic": {"vect_n": 63.9483, + "vect_e": 76.76498, + "vect_z": 5.0}}} + + for coord in mag_out.keys(): + for loc_coord in ['magnetic', coord]: + # Ensure the location is reset + self.vdata.lat = 75.0 + self.vdata.lt = 22.0 + self.vdata.vect_n = 50.0 + self.vdata.vect_e = 86.5 + self.vdata.vect_z = 5.0 + + # Set the coordinates + self.vdata.vect_coord = coord + self.vdata.loc_coord = loc_coord + + with self.subTest(vect_coord=coord, loc_coord=loc_coord): + # Convert the location + self.vdata.update_vect_coords_to_mag( + self.ocb.dtime[self.vdata.ocb_ind], + hemisphere=self.ocb.hemisphere) + + # Evaluate the output + self.assertAlmostEqual( + float(self.vdata.vect_n), + mag_out[coord][loc_coord]['vect_n'], + places=4, msg="unexpected north component") + self.assertAlmostEqual( + float(self.vdata.vect_e), + mag_out[coord][loc_coord]['vect_e'], + places=4, msg="unexpected east component") + self.assertAlmostEqual( + float(self.vdata.vect_z), + mag_out[coord][loc_coord]['vect_z'], + places=4, msg="unexpected vertical component") + self.assertRegex(self.vdata.vect_coord, "magnetic") + self.assertRegex(self.vdata.loc_coord, "magnetic") + + if loc_coord in mag_out.keys(): + self.assertAlmostEqual( + float(self.vdata.lat), mag_out[loc_coord]['lat'], + places=4, msg="unexpected magnetic latitude") + self.assertAlmostEqual( + float(self.vdata.lt), mag_out[loc_coord]['lt'], + places=4, msg="unexpected MLT") + return + + def test_update_vect_and_loc_coords_array(self): + """Test the vector and location coordinate conversion for arrays.""" + mag_out = {"geocentric": {"lat": 69.6782, "lt": 21.41886, + "geocentric": {"vect_n": 64.00367, + "vect_e": 76.71884, + "vect_z": 5.0}, + "magnetic": {"vect_n": 78.60399, + "vect_e": 61.67384, + "vect_z": 5.0}}, + "geodetic": {"lat": 69.58256, "lt": 21.422598, + "magnetic": {"vect_n": 78.6003, + "vect_e": 61.6786, + "vect_z": 5.0}, + "geodetic": {"vect_n": 63.9483, + "vect_e": 76.76498, + "vect_z": 5.0}}} + + for coord in mag_out.keys(): + for loc_coord in ['magnetic', coord]: + for is_array in [True, False]: + # Ensure the location is reset + self.vdata.lat = [75.0, 75.0] + self.vdata.lt = [22.0, 22.0] + self.vdata.vect_n = [50.0, 50.0] + self.vdata.vect_e = [86.5, 86.5] + self.vdata.vect_z = [5.0, 5.0] + + # Set the coordinates + self.vdata.vect_coord = coord + self.vdata.loc_coord = loc_coord + + # Adjust if list-like input is not desired + if is_array: + self.vdata.lat = numpy.asarray(self.vdata.lat) + self.vdata.lt = numpy.asarray(self.vdata.lt) + self.vdata.vect_n = numpy.asarray(self.vdata.vect_n) + self.vdata.vect_e = numpy.asarray(self.vdata.vect_e) + self.vdata.vect_z = numpy.asarray(self.vdata.vect_z) + + with self.subTest(vect_coord=coord, loc_coord=loc_coord, + is_array=is_array): + # Convert the location + self.vdata.update_vect_coords_to_mag( + self.ocb.dtime[self.vdata.ocb_ind], + hemisphere=self.ocb.hemisphere) + + # Evaluate the output + self.assertRegex(self.vdata.vect_coord, "magnetic") + self.assertRegex(self.vdata.loc_coord, "magnetic") + for i, lat in enumerate(self.vdata.lat): + self.assertAlmostEqual( + self.vdata.vect_n[i], + mag_out[coord][loc_coord]['vect_n'], + places=4, msg="unexpected north component") + self.assertAlmostEqual( + self.vdata.vect_e[i], + mag_out[coord][loc_coord]['vect_e'], + places=4, msg="unexpected east component") + self.assertAlmostEqual( + self.vdata.vect_z[i], + mag_out[coord][loc_coord]['vect_z'], + places=4, msg="unexpected vertical component") + + if loc_coord in mag_out.keys(): + self.assertAlmostEqual( + lat, mag_out[loc_coord]['lat'], places=4, + msg="unexpected magnetic latitude") + self.assertAlmostEqual( + self.vdata.lt[i], mag_out[loc_coord]['lt'], + places=4, msg="unexpected MLT") + return + + def test_update_vect_coords_mult_times(self): + """Test the vector coordinate conversion for multiple times.""" + mag_out = {"geocentric": {"vect_n": [78.60399, 77.03392], + "vect_e": [61.67384, 63.624099], + "vect_z": [5.0, 5.0]}, + "geodetic": {"vect_n": [78.6003, 77.0303], + "vect_e": [61.6786, 63.6284], + "vect_z": [5.0, 5.0]}} + + # Set multiple boundary indices + rec_inds = [self.ocb.rec_ind] + if hasattr(self.ocb, "get_next_good_ocb_ind"): + self.ocb.get_next_good_ocb_ind() + else: + self.ocb.rec_ind += 1 + rec_inds.append(self.ocb.rec_ind) + self.vdata.ocb_ind = rec_inds + + for coord in mag_out.keys(): + self.vdata.vect_coord = coord + + # Ensure the location is reset + self.vdata.vect_n = 50.0 + self.vdata.vect_e = 86.5 + self.vdata.vect_z = 5.0 + + with self.subTest(coord=coord): + # Convert the location + self.vdata.update_vect_coords_to_mag( + self.ocb.dtime[self.vdata.ocb_ind], + hemisphere=self.ocb.hemisphere) + + # Evaluate the output + for i, north in enumerate(self.vdata.vect_n): + self.assertAlmostEqual( + north, mag_out[coord]['vect_n'][i], places=4, + msg="unexpected north component") + self.assertAlmostEqual( + self.vdata.vect_e[i], mag_out[coord]['vect_e'][i], + places=4, msg="unexpected east component") + self.assertAlmostEqual( + self.vdata.vect_z[i], mag_out[coord]['vect_z'][i], + places=4, msg="unexpected vertical component") + self.assertRegex(self.vdata.vect_coord, "magnetic") + return + class TestDualScalingMethods(TestOCBScalingMethods): """Unit tests for the VectorData class.""" From c3017ca1e429a1c1887a72dbbc3bc50db9eea43e Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 16:50:18 -0400 Subject: [PATCH 55/60] STY: remove extra whitespace Remove extra whitespace from a blank line. --- ocbpy/vectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocbpy/vectors.py b/ocbpy/vectors.py index c7a101cb..cd90e583 100644 --- a/ocbpy/vectors.py +++ b/ocbpy/vectors.py @@ -340,7 +340,7 @@ def calc_dest_polar_angle(pole_quad, vect_quad, base_naz_angle, pole_angle): # Initialise the output dest_naz_angle = np.full(shape=(base_naz_angle + pole_angle + abs_mask).shape, fill_value=np.nan) - + # Calculate OCB polar angle based on the quadrants and other angles if np.any(abs_mask): if len(dest_naz_angle.shape) == 0: From 377e5ad9cd0bd17f24ae7fd0573b12c244a59b85 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Tue, 7 May 2024 18:00:48 -0400 Subject: [PATCH 56/60] TST: more `ocb_scaling` coverage Added coverage for two remaining uncovered lines. --- ocbpy/tests/test_ocb_scaling.py | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/ocbpy/tests/test_ocb_scaling.py b/ocbpy/tests/test_ocb_scaling.py index d945620f..f7bbb5a0 100644 --- a/ocbpy/tests/test_ocb_scaling.py +++ b/ocbpy/tests/test_ocb_scaling.py @@ -944,6 +944,35 @@ def test_update_loc_coords_mult_times(self): self.assertRegex(self.vdata.loc_coord, coord) return + def test_update_vect_coords_missing_data(self): + """Test no vector update if no magnetic location available.""" + # Ensure data is missing + self.vdata.lat = numpy.nan + + # Save the expected output + self.wdata.vect_n = self.vdata.vect_n + self.wdata.vect_e = self.vdata.vect_e + self.wdata.vect_z = self.vdata.vect_z + + # Cycle through the coordinate options + for coord in ["geocentric", "geodetic"]: + for loc_coord in ['magnetic', coord]: + self.vdata.vect_coord = coord + self.vdata.loc_coord = loc_coord + + with self.subTest(vect_coord=coord, loc_coord=loc_coord): + # Convert the location + self.vdata.update_vect_coords_to_mag( + self.ocb.dtime[self.vdata.ocb_ind], + hemisphere=self.ocb.hemisphere) + + # Test the vector output is unchanged + self.assertRegex(self.vdata.vect_coord, coord) + self.assertAlmostEqual(self.wdata.vect_n, self.vdata.vect_n) + self.assertAlmostEqual(self.wdata.vect_e, self.vdata.vect_e) + self.assertAlmostEqual(self.wdata.vect_z, self.vdata.vect_z) + return + def test_update_vect_and_loc_coords_float(self): """Test the vector and location coordinate conversion for floats.""" mag_out = {"geocentric": {"lat": 69.6782, "lt": 21.41886, @@ -1568,6 +1597,20 @@ def test_bad_loc_coord_for_vec_pole_angle(self): self.vdata.calc_vec_pole_angle() return + def test_bad_loc_coord_inputs(self): + """Test failure to convert location with mismatched dimensions.""" + self.vdata = ocbpy.ocb_scaling.VectorData([0, 1, 2], self.ocb.rec_ind, + [75.0, 74.0, 73.0], + [22.0, 2.0, 1.0], vect_n=50.0, + vect_e=86.5, vect_z=5.0, + dat_name="Test", + dat_units="$m s^{-1}$", + loc_coord='geodetic') + + with self.assertRaisesRegex(ValueError, "mismatched time and location"): + self.vdata.update_loc_coords(self.ocb.dtime[:2]) + return + class TestHaversine(unittest.TestCase): """Unit tests for the haversine functions.""" From b6a37270b625df7c374e0243080e3010393a3796 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 8 May 2024 08:40:26 -0400 Subject: [PATCH 57/60] BUG: fixed pysat Instrument functions Fixed the `add_ocb_to_data` function in `pysat_instruments` by: - adding error catch for badly shaped array-like height input for pandas instruments, - removing an unreachable section of code to add time to LT data, - adding an evaluation to avoid unnecessary calls to meshgrid, and - adding an extra meshgrid call for reshaping with height to reduce memory use. --- ocbpy/instruments/pysat_instruments.py | 31 ++++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/ocbpy/instruments/pysat_instruments.py b/ocbpy/instruments/pysat_instruments.py index 8592cda9..8a8b58be 100644 --- a/ocbpy/instruments/pysat_instruments.py +++ b/ocbpy/instruments/pysat_instruments.py @@ -336,19 +336,14 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Ensure the LT, Lat, and Height data are the same shape if lat.shape != lt.shape or lat.shape[0] != pysat_inst.index.shape[ 0] or (height.shape != lat.shape and len(height.shape) > 0): + if pysat_inst.pandas_format: + raise ValueError('unexpected height shape or bad lat/lt data') + + # Use local time to set the initial coordinates, since it will have + # UT dependence and latitude may not ocb_coords = [lt_coord for lt_coord in pysat_inst[mlt_name].coords.keys()] - if pysat_inst.index.name in ocb_coords: - combo_shape = list(lt.shape) - else: - # Ensure Local Time has universal time dependence - ocb_coords.insert(0, pysat_inst.index.name) - combo_shape = [pysat_inst.index.shape[0]] - combo_shape.extend(list(lt.shape)) - out_lt, _ = np.meshgrid(lt, pysat_inst.index) - - if out_lt.shape != combo_shape: - lt = out_lt.reshape(combo_shape) + combo_shape = list(lt.shape) # Expand the coordinates if the lat coordinates are not the # same as the LT coordinates or height coordinatess @@ -357,10 +352,11 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', combo_shape.append(pysat_inst[lat_coord].shape[0]) ocb_coords.append(lat_coord) - # Reshape the latitude and local time data - out_lat, out_lt = np.meshgrid(lat, lt) - lat = out_lat.reshape(combo_shape) - lt = out_lt.reshape(combo_shape) + # Reshape the latitude and local time data if necessary + if lat.shape != tuple(combo_shape) or lt.shape != tuple(combo_shape): + out_lat, out_lt = np.meshgrid(lat, lt) + lat = out_lat.reshape(combo_shape) + lt = out_lt.reshape(combo_shape) # Determine if reshaping for altitude is necessary if len(height.shape) == 0: @@ -376,10 +372,11 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', # Reshape the data if new_coords: - out_lat, out_lt, out_height = np.meshgrid(lat, lt, height) + out_lat, out_height = np.meshgrid(lat, height) + out_lt, _ = np.meshgrid(lt, height) lat = out_lat.reshape(combo_shape) - lt = out_lt.reshape(combo_shape) height = out_height.reshape(combo_shape) + lt = out_lt.reshape(combo_shape) else: height = height.reshape(combo_shape) elif len(height.shape) == len(lat.shape): From ec3a6295912d32aea1aaaf3887a7066e5fbc8af0 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 8 May 2024 08:40:53 -0400 Subject: [PATCH 58/60] TST: added pysat tests Added tests for `pysat_instruments` to cover missed lines. --- ocbpy/tests/test_pysat.py | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/ocbpy/tests/test_pysat.py b/ocbpy/tests/test_pysat.py index 4127825e..f10d3e60 100644 --- a/ocbpy/tests/test_pysat.py +++ b/ocbpy/tests/test_pysat.py @@ -346,6 +346,26 @@ def tearDown(self): PysatBase.tearDown(self) return + def test_empty_ocb_warning(self): + """Test log error raised if OCB has no records.""" + # Load the boundaries and instrument + self.load_instrument() + + # Reload the boundary without records + self.ocb_kw['stime'] = self.test_module._test_dates[''][''] + self.ocb_kw['etime'] = self.ocb_kw['stime'] + self.load_boundaries() + + # Test adding OCBs + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, ocb=self.ocb, + max_sdiff=self.del_time) + + # Check for the logging error + self.lwarn = u"no data in Boundary file" + self.eval_logging_message() + return + def test_add_ocb_to_metadata(self): """Test the metadata adding routine.""" # Load the data and boundaries @@ -722,6 +742,22 @@ def test_add_ocb_to_data_bad_vector_name(self): ocb=self.ocb) return + def test_bad_height_array(self): + """Test failure with a badly shaped height input.""" + # Load the data and boundaries + self.load_instrument() + + # Define an array height input + height = np.full(shape=(self.test_inst[self.pysat_lat].values.shape[0], + 3), fill_value=200.0) + + # Raise the desired error + with self.assertRaisesRegex(ValueError, 'unexpected height shape'): + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height=height, ocb=self.ocb, + max_sdiff=self.del_time) + return + @unittest.skipIf(no_pysat, "pysat not installed, cannot test routines") class TestPysatMethodsEAB(TestPysatMethods): @@ -798,6 +834,71 @@ def setUp(self): self.pysat_alt = '' return + def test_add_ocb_to_data_evar_with_alt_coord(self): + """Test adding ocb to pysat with altitude coordinate.""" + # Load the data and boundaries + self.load_instrument() + + # Add the OCB with E-scaled variables and the height variable + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name='altitude', + evar_names=[self.pysat_var2], + ocb=self.ocb, max_sdiff=self.del_time) + + self.set_new_keys(exclude_r_corr=False) + self.assertIn('r_corr', self.pysat_keys) + self.pysat_keys[self.pysat_keys.index("r_corr")] = None + + self.test_ocb_added() + return + + def test_add_ocb_to_data_evar_with_alt_array(self): + """Test adding ocb to pysat with array altitude input.""" + # Load the data and boundaries + self.load_instrument() + + # Define an array height input + height = np.full(shape=self.test_inst[self.pysat_key].values.shape, + fill_value=200.0) + + # Add the OCB with E-scaled variables + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height=height, evar_names=[self.pysat_key], + ocb=self.ocb, max_sdiff=self.del_time) + + self.set_new_keys(exclude_r_corr=False) + self.assertIn('r_corr', self.pysat_keys) + self.pysat_keys[self.pysat_keys.index("r_corr")] = None + + self.test_ocb_added() + return + + def test_add_ocb_to_data_evar_with_alt_value(self): + """Test adding ocb to pysat with array altitude input.""" + # Load the data and boundaries + self.load_instrument() + + # Define a non-coordinate height + dims = list(self.test_inst[self.pysat_key].dims) + dims.reverse() + shape = list(self.test_inst[self.pysat_key].values.shape) + shape.reverse() + self.test_inst.data = self.test_inst.data.assign( + {'dummy1_alt': (dims, np.full(shape=shape, fill_value=200.0))}) + + # Add the OCB with E-scaled variables + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name="dummy1_alt", + evar_names=[self.pysat_key], + ocb=self.ocb, max_sdiff=self.del_time) + + self.set_new_keys(exclude_r_corr=False) + self.assertIn('r_corr', self.pysat_keys) + self.pysat_keys[self.pysat_keys.index("r_corr")] = None + + self.test_ocb_added() + return + def test_mismatched_vector_data(self): """Test that vector data with different dimensions fails.""" # Load the data and boundaries From 362dfdab3cfea8a3d56be23ad9705e4953bfc77c Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 8 May 2024 10:50:44 -0400 Subject: [PATCH 59/60] ENH: improved padding robustness Improved the robustness of the padding function to catch more potential errors. --- ocbpy/instruments/pysat_instruments.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/ocbpy/instruments/pysat_instruments.py b/ocbpy/instruments/pysat_instruments.py index 8a8b58be..aa2526f5 100644 --- a/ocbpy/instruments/pysat_instruments.py +++ b/ocbpy/instruments/pysat_instruments.py @@ -495,17 +495,11 @@ def add_ocb_to_data(pysat_inst, mlat_name='', mlt_name='', height_name='', vector_init[ikey] = reshape_pad_mask_flatten( pysat_inst[vname], time_mask) - # Save the vector shapes for testing if vector_init[ikey].shape not in vshape: vshape.append(vector_init[ikey].shape) else: vector_init[ikey] = vname - # Evaluate the consistency of the vector inputs - if len(vshape) > 1: - raise ValueError( - 'vector variables must all have the same shape') - # Perform the vector scaling vout = ocbscal.VectorData(vind, ocb.rec_ind, lat[iout], lt[iout], **vector_init) @@ -604,24 +598,26 @@ def reshape_pad_mask_flatten(data, mask): """ if np.all(mask.dims == data.dims): - flat = data.where(mask, drop=True).values.flatten() + if mask.shape == data.shape: + flat = data.where(mask, drop=True).values.flatten() + else: + raise ValueError('different shapes for the same dimesions') else: # Reshape this data variable for existing dims data_dims = [dim for dim in mask.dims if dim in data.dims] - flat = data.transpose(*data_dims).values + flat = data.transpose(*data_dims, ...).values # Pad by adding the additional dimensions if needed if len(data_dims) < len(mask.dims): try: flat = np.full(shape=tuple(reversed(list(mask.shape))), fill_value=flat.transpose()).transpose() - except Exception as aerr: + except Exception as xerr: # Using Exception instead of AssertionError because the # catch is not consistent raise ValueError(''.join(['vector variables must all have the', - ' same shape, {:}'.format(aerr)])) + ' same shape, {:}'.format(xerr)])) - # Downselect and flatten the data flat = flat[mask.values].flatten() return flat From 2680c389304c6d96374fd7cbe31d0b2382f2e6cd Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Wed, 8 May 2024 10:51:38 -0400 Subject: [PATCH 60/60] TST: added test for `reshape_pad_mask_flatten` Added unit tests for `reshape_pad_mask_flatten`. --- ocbpy/tests/test_pysat.py | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/ocbpy/tests/test_pysat.py b/ocbpy/tests/test_pysat.py index f10d3e60..b5e9e12e 100644 --- a/ocbpy/tests/test_pysat.py +++ b/ocbpy/tests/test_pysat.py @@ -18,6 +18,7 @@ import pysat import ocbpy.instruments.pysat_instruments as ocb_pysat import pandas as pds + import xarray as xr no_pysat = False except ImportError: no_pysat = True @@ -819,6 +820,26 @@ def setUp(self): return + def test_bad_vector_shape(self): + """Test failure with badly shaped vector data.""" + # Load the data and boundaries + self.load_instrument() + + # Raise the desired value error + with self.assertRaisesRegex(ValueError, + 'mismatched dimensions for VectorData'): + ocb_pysat.add_ocb_to_data(self.test_inst, self.pysat_lat, "mlt", + height_name=self.pysat_alt, ocb=self.ocb, + vector_names={ + 'profile': + {'vect_n': 'profiles', + 'vect_e': 'variable_profiles', + 'dat_name': 'profile', + 'dat_units': 'unit', + 'scale_func': None}}, + max_sdiff=self.del_time) + return + @unittest.skipIf(no_pysat, "pysat not installed") class TestPysatMethodsModel(TestPysatMethods): @@ -1199,3 +1220,54 @@ def setUp(self): self.pysat_alt = '' self.cust_kwargs['height_name'] = self.pysat_alt return + + +@unittest.skipIf(no_pysat, "pysat not installed") +class TestPysatReshape(unittest.TestCase): + """Unit tests for the `reshape_pad_mask_flatten` function.""" + + def setUp(self): + """Set up the test environment.""" + self.shape = [4, 5] + self.data = xr.DataArray(data=np.ones(shape=self.shape), + dims=['x', 'y']) + self.flat = None + return + + def tearDown(self): + """Clean up the test environment.""" + del self.shape, self.data, self.flat + return + + def test_reshape_pad_mask_flatten(self): + """Test successful data padding, masking, and flattening.""" + + for mask_shape, dims in [(self.shape, ['x', 'y']), + (tuple(reversed(self.shape)), ['y', 'x']), + (self.shape + [3], ['x', 'y', 'z'])]: + mask = xr.DataArray(data=np.ones(shape=mask_shape, dtype=bool), + dims=dims) + with self.subTest(mask_dims=dims): + self.flat = ocb_pysat.reshape_pad_mask_flatten( + self.data, mask) + + self.assertEqual(self.flat.shape, np.prod(mask_shape)) + self.assertGreaterEqual(self.flat.shape, np.prod(self.shape)) + return + + def test_bad_reshape_pad_mask_flatten(self): + """Test data padding, masking, and flattening failure.""" + # Change the mask shape, and add one extra dimension + self.shape[0] += 2 + + for mask_shape, dims, verr in [ + (self.shape, ['x', 'y'], "different shapes for the same dim"), + (self.shape + [3], ['x', 'y', 'z'], "vector variables must "), + (self.shape, ['z', 'y'], "vector variables must all have")]: + mask = xr.DataArray(data=np.ones(shape=mask_shape, dtype=bool), + dims=dims) + + with self.subTest(mask_shape=mask_shape, dims=dims): + with self.assertRaisesRegex(ValueError, verr): + ocb_pysat.reshape_pad_mask_flatten(self.data, mask) + return