Skip to content

Commit

Permalink
Merge branch 'develop' into mnt/modularize-rocket-draw
Browse files Browse the repository at this point in the history
  • Loading branch information
MateusStano authored Apr 25, 2024
2 parents 822a89e + 14375ed commit 2cbaa77
Show file tree
Hide file tree
Showing 19 changed files with 170 additions and 172 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

- ENH: Exponential backoff decorator (fix #449) [#588](https://github.com/RocketPy-Team/RocketPy/pull/588)
- ENH: Add new stability margin properties to Flight class [#572](https://github.com/RocketPy-Team/RocketPy/pull/572)
- ENH: adds `Function.remove_outliers` method [#554](https://github.com/RocketPy-Team/RocketPy/pull/554)

### Changed

- DEP: delete deprecated rocketpy.tools.cached_property [#587](https://github.com/RocketPy-Team/RocketPy/pull/587)
- MNT: Modularize Rocket Draw [#580](https://github.com/RocketPy-Team/RocketPy/pull/580)
- DOC: Improvements of Environment docstring phrasing [#565](https://github.com/RocketPy-Team/RocketPy/pull/565)
- MNT: Refactor flight prints module [#579](https://github.com/RocketPy-Team/RocketPy/pull/579)
Expand All @@ -46,6 +48,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- BUG: Swap rocket.total_mass.differentiate for motor.total_mass_flow rate [#585](https://github.com/RocketPy-Team/RocketPy/pull/585)
- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559)

## [v1.2.2] - 2024-03-22
Expand Down
198 changes: 115 additions & 83 deletions rocketpy/environment/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
import warnings
from collections import namedtuple
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

import numpy as np
import numpy.ma as ma
Expand All @@ -13,6 +13,7 @@
from ..mathutils.function import Function, funcify_method
from ..plots.environment_plots import _EnvironmentPlots
from ..prints.environment_prints import _EnvironmentPrints
from ..tools import exponential_backoff

try:
import netCDF4
Expand Down Expand Up @@ -680,18 +681,9 @@ def set_elevation(self, elevation="Open-Elevation"):

# self.elevation = elev

elif self.latitude != None and self.longitude != None:
try:
print("Fetching elevation from open-elevation.com...")
request_url = "https://api.open-elevation.com/api/v1/lookup?locations={:f},{:f}".format(
self.latitude, self.longitude
)
response = requests.get(request_url)
results = response.json()["results"]
self.elevation = results[0]["elevation"]
print("Elevation received:", self.elevation)
except:
raise RuntimeError("Unable to reach Open-Elevation API servers.")
elif self.latitude is not None and self.longitude is not None:
self.elevation = self.__fetch_open_elevation()
print("Elevation received: ", self.elevation)
else:
raise ValueError(
"Latitude and longitude must be set to use"
Expand Down Expand Up @@ -1303,26 +1295,8 @@ def set_atmospheric_model(
"v_wind": "vgrdprs",
}
# Attempt to get latest forecast
time_attempt = datetime.utcnow()
success = False
attempt_count = 0
while not success and attempt_count < 10:
time_attempt -= timedelta(hours=6 * attempt_count)
file = "https://nomads.ncep.noaa.gov/dods/gens_bc/gens{:04d}{:02d}{:02d}/gep_all_{:02d}z".format(
time_attempt.year,
time_attempt.month,
time_attempt.day,
6 * (time_attempt.hour // 6),
)
try:
self.process_ensemble(file, dictionary)
success = True
except OSError:
attempt_count += 1
if not success:
raise RuntimeError(
"Unable to load latest weather data for GEFS through " + file
)
self.__fetch_gefs_ensemble(dictionary)

elif file == "CMC":
# Define dictionary
dictionary = {
Expand All @@ -1338,27 +1312,7 @@ def set_atmospheric_model(
"u_wind": "ugrdprs",
"v_wind": "vgrdprs",
}
# Attempt to get latest forecast
time_attempt = datetime.utcnow()
success = False
attempt_count = 0
while not success and attempt_count < 10:
time_attempt -= timedelta(hours=12 * attempt_count)
file = "https://nomads.ncep.noaa.gov/dods/cmcens/cmcens{:04d}{:02d}{:02d}/cmcens_all_{:02d}z".format(
time_attempt.year,
time_attempt.month,
time_attempt.day,
12 * (time_attempt.hour // 12),
)
try:
self.process_ensemble(file, dictionary)
success = True
except OSError:
attempt_count += 1
if not success:
raise RuntimeError(
"Unable to load latest weather data for CMC through " + file
)
self.__fetch_cmc_ensemble(dictionary)
# Process other forecasts or reanalysis
else:
# Check if default dictionary was requested
Expand Down Expand Up @@ -1650,20 +1604,7 @@ def process_windy_atmosphere(self, model="ECMWF"):
model.
"""

# Process the model string
model = model.lower()
if model[-1] == "u": # case iconEu
model = "".join([model[:4], model[4].upper(), model[4 + 1 :]])
# Load data from Windy.com: json file
url = f"https://node.windy.com/forecast/meteogram/{model}/{self.latitude}/{self.longitude}/?step=undefined"
try:
response = requests.get(url).json()
except:
if model == "iconEu":
raise ValueError(
"Could not get a valid response for Icon-EU from Windy. Check if the latitude and longitude coordinates set are inside Europe.",
)
raise
response = self.__fetch_atmospheric_data_from_windy(model)

# Determine time index from model
time_array = np.array(response["data"]["hours"])
Expand Down Expand Up @@ -1824,18 +1765,7 @@ def process_wyoming_sounding(self, file):
None
"""
# Request Wyoming Sounding from file url
response = requests.get(file)
if response.status_code != 200:
raise ImportError("Unable to load " + file + ".")
if len(re.findall("Can't get .+ Observations at", response.text)):
raise ValueError(
re.findall("Can't get .+ Observations at .+", response.text)[0]
+ " Check station number and date."
)
if response.text == "Invalid OUTPUT: specified\n":
raise ValueError(
"Invalid OUTPUT: specified. Make sure the output is Text: List."
)
response = self.__fetch_wyoming_sounding(file)

# Process Wyoming Sounding by finding data table and station info
response_split_text = re.split("(<.{0,1}PRE>)", response.text)
Expand Down Expand Up @@ -1961,9 +1891,7 @@ def process_noaaruc_sounding(self, file):
None
"""
# Request NOAA Ruc Sounding from file url
response = requests.get(file)
if response.status_code != 200 or len(response.text) < 10:
raise ImportError("Unable to load " + file + ".")
response = self.__fetch_noaaruc_sounding(file)

# Split response into lines
lines = response.text.split("\n")
Expand Down Expand Up @@ -3538,6 +3466,110 @@ def set_earth_geometry(self, datum):
f"The reference system {datum} for Earth geometry " "is not recognized."
)

# Auxiliary functions - Fetching Data from 3rd party APIs

@exponential_backoff(max_attempts=3, base_delay=1, max_delay=60)
def __fetch_open_elevation(self):
print("Fetching elevation from open-elevation.com...")
request_url = (
"https://api.open-elevation.com/api/v1/lookup?locations"
f"={self.latitude},{self.longitude}"
)
try:
response = requests.get(request_url)
except Exception as e:
raise RuntimeError("Unable to reach Open-Elevation API servers.")
results = response.json()["results"]
return results[0]["elevation"]

@exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
def __fetch_atmospheric_data_from_windy(self, model):
model = model.lower()
if model[-1] == "u": # case iconEu
model = "".join([model[:4], model[4].upper(), model[4 + 1 :]])
url = (
f"https://node.windy.com/forecast/meteogram/{model}/"
f"{self.latitude}/{self.longitude}/?step=undefined"
)
try:
response = requests.get(url).json()
except Exception as e:
if model == "iconEu":
raise ValueError(
"Could not get a valid response for Icon-EU from Windy. "
"Check if the coordinates are set inside Europe."
)
return response

@exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
def __fetch_wyoming_sounding(self, file):
response = requests.get(file)
if response.status_code != 200:
raise ImportError(f"Unable to load {file}.")
if len(re.findall("Can't get .+ Observations at", response.text)):
raise ValueError(
re.findall("Can't get .+ Observations at .+", response.text)[0]
+ " Check station number and date."
)
if response.text == "Invalid OUTPUT: specified\n":
raise ValueError(
"Invalid OUTPUT: specified. Make sure the output is Text: List."
)
return response

@exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
def __fetch_noaaruc_sounding(self, file):
response = requests.get(file)
if response.status_code != 200 or len(response.text) < 10:
raise ImportError("Unable to load " + file + ".")

@exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
def __fetch_gefs_ensemble(self, dictionary):
time_attempt = datetime.now(tz=timezone.utc)
success = False
attempt_count = 0
while not success and attempt_count < 10:
time_attempt -= timedelta(hours=6 * attempt_count)
file = (
f"https://nomads.ncep.noaa.gov/dods/gens_bc/gens"
f"{time_attempt.year:04d}{time_attempt.month:02d}"
f"{time_attempt.day:02d}/"
f"gep_all_{6 * (time_attempt.hour // 6):02d}z"
)
try:
self.process_ensemble(file, dictionary)
success = True
except OSError:
attempt_count += 1
if not success:
raise RuntimeError(
"Unable to load latest weather data for GEFS through " + file
)

@exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
def __fetch_cmc_ensemble(self, dictionary):
# Attempt to get latest forecast
time_attempt = datetime.now(tz=timezone.utc)
success = False
attempt_count = 0
while not success and attempt_count < 10:
time_attempt -= timedelta(hours=12 * attempt_count)
file = (
f"https://nomads.ncep.noaa.gov/dods/cmcens/"
f"cmcens{time_attempt.year:04d}{time_attempt.month:02d}"
f"{time_attempt.day:02d}/"
f"cmcens_all_{12 * (time_attempt.hour // 12):02d}z"
)
try:
self.process_ensemble(file, dictionary)
success = True
except OSError:
attempt_count += 1
if not success:
raise RuntimeError(
"Unable to load latest weather data for CMC through " + file
)

# Auxiliary functions - Geodesic Coordinates

@staticmethod
Expand Down
6 changes: 1 addition & 5 deletions rocketpy/environment/environment_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import datetime
import json
from collections import defaultdict
from functools import cached_property

import netCDF4
import numpy as np
Expand All @@ -22,11 +23,6 @@
from ..units import convert_units
from .environment import Environment

try:
from functools import cached_property
except ImportError:
from ..tools import cached_property

# TODO: the average_wind_speed_profile_by_hour and similar methods could be more abstract than currently are


Expand Down
6 changes: 1 addition & 5 deletions rocketpy/mathutils/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@
import warnings
from collections.abc import Iterable
from copy import deepcopy
from functools import cached_property
from inspect import signature
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from scipy import integrate, linalg, optimize

try:
from functools import cached_property
except ImportError:
from ..tools import cached_property

NUMERICAL_TYPES = (float, int, complex, np.ndarray, np.integer, np.floating)


Expand Down
3 changes: 1 addition & 2 deletions rocketpy/mathutils/vector_matrix.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from cmath import isclose
from functools import cached_property
from itertools import product

from rocketpy.tools import cached_property


class Vector:
"""Pure python basic R3 vector class designed for simple operations.
Expand Down
7 changes: 2 additions & 5 deletions rocketpy/motors/hybrid_motor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from functools import cached_property

from rocketpy.tools import parallel_axis_theorem_from_com

from ..mathutils.function import Function, funcify_method, reset_funcified_methods
Expand All @@ -7,11 +9,6 @@
from .motor import Motor
from .solid_motor import SolidMotor

try:
from functools import cached_property
except ImportError:
from ..tools import cached_property


class HybridMotor(Motor):
"""Class to specify characteristics and useful operations for Hybrid
Expand Down
7 changes: 1 addition & 6 deletions rocketpy/motors/liquid_motor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import warnings
from functools import cached_property

import numpy as np

Expand All @@ -13,11 +13,6 @@
from ..prints.liquid_motor_prints import _LiquidMotorPrints
from .motor import Motor

try:
from functools import cached_property
except ImportError:
from ..tools import cached_property


class LiquidMotor(Motor):
"""Class to specify characteristics and useful operations for Liquid
Expand Down
6 changes: 1 addition & 5 deletions rocketpy/motors/motor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import warnings
from abc import ABC, abstractmethod
from functools import cached_property

import numpy as np

Expand All @@ -9,11 +10,6 @@
from ..prints.motor_prints import _MotorPrints
from ..tools import parallel_axis_theorem_from_com, tuple_handler

try:
from functools import cached_property
except ImportError:
from ..tools import cached_property


class Motor(ABC):
"""Abstract class to specify characteristics and useful operations for
Expand Down
Loading

0 comments on commit 2cbaa77

Please sign in to comment.