def test_run_options(input_data): # Default options is all 1's, so make sure they are equivalent assert_array_equal(msis.run(*input_data, options=None), msis.run(*input_data, options=[1] * 25)) with pytest.raises(ValueError, match='options needs to be a list'): msis.run(*input_data, options=[1] * 22)
def test_run_versions(input_data): # Make sure we accept these version numbers and don't throw an error for ver in [0, 2, '00', '2']: msis.run(*input_data, version=ver) # Test for something outside of that list for an error to be thrown with pytest.raises(ValueError, match='The MSIS version selected'): msis.run(*input_data, version=1)
def test_run_wrapped_lon(input_data, expected_output): date, _, lat, alt, f107, f107a, ap = input_data input_data = (date, -180, lat, alt, f107, f107a, ap) output1 = msis.run(*input_data) input_data = (date, 180, lat, alt, f107, f107a, ap) output2 = msis.run(*input_data) assert_allclose(output1, output2, rtol=1e-5) input_data = (date, 0, lat, alt, f107, f107a, ap) output1 = msis.run(*input_data) input_data = (date, 360, lat, alt, f107, f107a, ap) output2 = msis.run(*input_data) assert_allclose(output1, output2, rtol=1e-5)
def run_msis(dates, lons, lats, alts, f107s, f107as, aps, options): """Call MSIS looping over all possible inputs. Parameters ---------- dates : list of dates Date and time to calculate the output at. lons : list of floats Longitudes to calculate the output at. lats : list of floats Latitudes to calculate the output at. alts : list of floats Altitudes to calculate the output at. f107s : list of floats F107 value for the given date(s). f107as : list of floats F107 running 100-day average for the given date(s). aps : list of floats Ap for the given date(s). options : list of floats (length 25) A list of options (switches) to the model """ # 11 == 10 densities + 1 temperature # Output density: He, O, N2, O2, Ar, Total (gm/cm3), H, N, Anomalous O # Output temp: exospheric, specific altitude _, input_data = msis.create_input(dates, lons, lats, alts, f107s, f107as, aps) output = msis.run(dates, lons, lats, alts, f107s, f107as, aps, options) # Force to float, JSON serializer in future calls does not work # with float32 output # Return the altitutdes, latitudes, longitudes with the data return (input_data[:, 2:5], output.reshape(-1, 11).astype(np.float))
def test_run_poles(input_data): # Test that moving in longitude around a pole # returns the same values # North pole date, _, _, alt, f107, f107a, ap = input_data input_data = (date, 0, 90, alt, f107, f107a, ap) output1 = msis.run(*input_data) input_data = (date, 20, 90, alt, f107, f107a, ap) output2 = msis.run(*input_data) assert_allclose(output1, output2, rtol=1e-5) # South pole input_data = (date, 0, -90, alt, f107, f107a, ap) output1 = msis.run(*input_data) input_data = (date, 20, -90, alt, f107, f107a, ap) output2 = msis.run(*input_data) assert_allclose(output1, output2, rtol=1e-5)
def test_run_multi_point00(input_data, expected_output00): date, lon, lat, alt, f107, f107a, ap = input_data # 5 x 5 surface input_data = (date, [lon] * 5, [lat] * 5, alt, f107, f107a, ap) output = msis.run(*input_data, version=0) assert output.shape == (1, 5, 5, 1, 11) expected = np.tile(expected_output00, (5, 5, 1)) assert_allclose(np.squeeze(output), expected, rtol=1e-5)
def test_run_multi_point(input_data, expected_output): # test multi-point run, like a satellite fly-through # and make sure we don't grid the input data # 5 input points date, lon, lat, alt, f107, f107a, ap = input_data input_data = ([date] * 5, [lon] * 5, [lat] * 5, [alt] * 5, [f107] * 5, [f107a] * 5, ap * 5) output = msis.run(*input_data) assert output.shape == (5, 11) expected = np.tile(expected_output, (5, 1)) assert_allclose(np.squeeze(output), expected, rtol=1e-5)
def run_input_line(line): items = line.split() expected = np.array(items[9:], dtype=np.float32) # Needs to be "-3:" due to strip() taking off leading spaces doy = int(items[0][-3:]) - 1 # Python wants DOY to start with 0 sec, alt, lat, lon, _, f107a, f107, ap = [float(x) for x in items[1:9]] ap = [[ap] * 7] year = int(items[0][:-3]) # Two digit year if year > 60: year += 1900 else: year += 2000 date = (np.datetime64(str(year)) + np.timedelta64(doy, 'D') + np.timedelta64(int(sec), 's')) test_inp = (date, lon, lat, alt, f107, f107a, ap) test_output = np.squeeze(msis.run(*test_inp)) # Rearrange the run's output to match the expected # ordering of elements x = np.array([ test_output[4], test_output[3], test_output[1], test_output[2], test_output[6], test_output[0], test_output[5], test_output[7], test_output[8], test_output[10], ]) # Different units in the test file (cgs) x[np.isclose(x, 9.9e-38, atol=1e-38)] = np.nan x[:-1] *= 1e-6 x[5] *= 1e3 x[np.isnan(x)] = 9.999e-38 # The output print statements are messy. # They technically give 4 decimal places, but only truly 3 decimal places # in scientific notation. Example: 0.8888e+23 -> 8.888e+24 # We will require relative comparisons of 2e-3 to account for the # last digit errors. assert_allclose(x, expected, rtol=2e-3)
def lambda_handler(event, context): """Handle the incoming API event and route properly.""" # Parse the incoming path and route the event properly if 'surface' in event['path']: return surface_handler(event, context) if 'altitude' in event['path']: return altitude_handler(event, context) # Data is passed in the body, so pull it out of there data = json.loads(event['body']) # Validate the event validate_event(data) # Convert the dates data['dates'] = [ datetime.strptime(x, "%Y-%m-%dT%H:%M") for x in data['dates'] ] # Initialize the model, default is all ones options = data.get("options", [1] * 25) if len(options) != 25: raise ValueError("options requires a length 25 array") # 1 Ap for each date, need to make it a n x 7 array aps = [[ap] * 7 for ap in data['aps']] # Call the main loop output = msis.run(data['dates'], data['lons'], data['lats'], data['alts'], data['f107s'], data['f107as'], aps, options) return { 'statusCode': 200, 'body': json.dumps(output.tolist()).replace("NaN", 'null'), "headers": { "Access-Control-Allow-Origin": "*", "content-type": "application/json" } }
import matplotlib.pyplot as plt import numpy as np from pymsis import msis lon = 0 lat = 70 alts = np.linspace(0, 1000, 1000) f107 = 150 f107a = 150 ap = 7 aps = [[ap]*7] date = np.datetime64('2003-01-01T00:00') output_midnight = msis.run(date, lon, lat, alts, f107, f107a, aps) date = np.datetime64('2003-01-01T12:00') output_noon = msis.run(date, lon, lat, alts, f107, f107a, aps) # output is now of the shape (1, 1, 1, 1000, 11) # Get rid of the single dimensions output_midnight = np.squeeze(output_midnight) output_noon = np.squeeze(output_noon) variables = ['Total mass density', 'N2', 'O2', 'O', 'He', 'H', 'Ar', 'N', 'Anomalous O', 'NO', 'Temperature'] _, ax = plt.subplots() for i, label in enumerate(variables): if label in ('NO', 'Total mass density', 'Temperature'): # There is currently no NO data, also ignore non-number densities
lons = range(-180, 185, 5) lats = range(-90, 95, 5) alt = 200 f107 = 150 f107a = 150 ap = 4 # Diurnal data dates = np.arange('2003-01-01', '2003-01-02', dtype='datetime64[30m]') ndates = len(dates) # (F107, F107a, ap) all need to be specified at the same length as dates f107s = [f107] * ndates f107as = [f107a] * ndates aps = [[ap] * 7] * ndates output = msis.run(dates, lons, lats, alt, f107s, f107as, aps) # output is now of the shape (ndates, nlons, nlats, 1, 11) # Get rid of the single dimensions output = np.squeeze(output) i = 7 # N fig, (ax_time, ax_mesh) = plt.subplots(nrows=2, gridspec_kw={'height_ratios': [1, 4]}, constrained_layout=True) xx, yy = np.meshgrid(lons, lats) vmin, vmax = 1e13, 8e13 norm = matplotlib.colors.Normalize(vmin, vmax) mesh = ax_mesh.pcolormesh(xx, yy, output[0, :, :, i].T,
""" import matplotlib.pyplot as plt import numpy as np from pymsis import msis lon = 0 lat = 70 alts = np.linspace(0, 1000, 1000) f107 = 150 f107a = 150 ap = 7 aps = [[ap] * 7] date = np.datetime64('2003-01-01T00:00') output_midnight2 = msis.run(date, lon, lat, alts, f107, f107a, aps) output_midnight0 = msis.run(date, lon, lat, alts, f107, f107a, aps, version=0) diff_midnight = (output_midnight2 - output_midnight0) / output_midnight0 * 100 date = np.datetime64('2003-01-01T12:00') output_noon2 = msis.run(date, lon, lat, alts, f107, f107a, aps) output_noon0 = msis.run(date, lon, lat, alts, f107, f107a, aps, version=0) diff_noon = (output_noon2 - output_noon0) / output_noon0 * 100 # output is now of the shape (1, 1, 1, 1000, 11) # Get rid of the single dimensions diff_midnight = np.squeeze(diff_midnight) diff_noon = np.squeeze(diff_noon) variables = [ 'Total mass density', 'N2', 'O2', 'O', 'He', 'H', 'Ar', 'N', 'Anomalous O',
import matplotlib.pyplot as plt import numpy as np from pymsis import msis lons = range(-180, 185, 5) lats = range(-90, 95, 5) alt = 200 f107 = 150 f107a = 150 ap = 7 # One years worth of data at the 12th hour every day date = np.datetime64('2003-01-01T12:00') aps = [[ap] * 7] output = msis.run(date, lons, lats, alt, f107, f107a, aps) # output is now of the shape (1, nlons, nlats, 1, 11) # Get rid of the single dimensions output = np.squeeze(output) i = 2 # O2 _, ax = plt.subplots() xx, yy = np.meshgrid(lons, lats) mesh = ax.pcolormesh(xx, yy, output[:, :, i].T, shading='auto') plt.colorbar(mesh, label='Number density (/m$^3$)') ax.set_title(f"O$_2$ number density at {alt} km") ax.set_xlabel("Longitude") ax.set_ylabel("Latitude")
def test_run_version00_low_altitude(input_data): # There is no O, H, N below 72.5 km, should be NaN date, lon, lat, _, f107, f107a, ap = input_data input_data = (date, lon, lat, 71, f107, f107a, ap) output = msis.run(*input_data, version=0) assert np.all(np.isnan(np.squeeze(output)[[3, 5, 7]]))
def test_run_version00(input_data, expected_output00): output = msis.run(*input_data, version=0) assert output.shape == (1, 11) assert_allclose(np.squeeze(output), expected_output00, rtol=1e-5)
import matplotlib.pyplot as plt import numpy as np from pymsis import msis lons = range(-180, 185, 5) lats = range(-90, 95, 5) alt = 200 f107 = 150 f107a = 150 ap = 7 # One years worth of data at the 12th hour every day date = np.datetime64('2003-01-01T12:00') aps = [[ap] * 7] output2 = msis.run(date, lons, lats, alt, f107, f107a, aps) output0 = msis.run(date, lons, lats, alt, f107, f107a, aps, version=0) diff = (output2 - output0) / output0 * 100 # diff is now of the shape (1, nlons, nlats, 1, 11) # Get rid of the single dimensions diff = np.squeeze(diff) variables = [ 'Total mass density', 'N2', 'O2', 'O', 'He', 'H', 'Ar', 'N', 'Anomalous O', 'NO', 'Temperature' ] fig, axarr = plt.subplots(nrows=3, ncols=3, constrained_layout=True, figsize=(8, 6))
def test_run_single_point(input_data, expected_output): output = msis.run(*input_data) assert output.shape == (1, 11) assert_allclose(np.squeeze(output), expected_output, rtol=1e-5)