Skip to content

Commit 9320675

Browse files
Merge branch 'develop' into rel/v1.1.0
2 parents be0a0ce + f29449a commit 9320675

File tree

7 files changed

+220
-89
lines changed

7 files changed

+220
-89
lines changed

.pylintrc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,18 @@ function-naming-style=snake_case
185185
#function-rgx=
186186

187187
# Good variable names which should always be accepted, separated by a comma.
188-
good-names=i,
189-
j,
190-
k,
191-
ex,
192-
Run,
193-
_
188+
good-names=FlightPhases,
189+
WindroseAxes,
190+
194191

195192
# Good variable names regexes, separated by a comma. If names match any regex,
196193
# they will always be accepted
197-
good-names-rgxs=
194+
good-names-rgxs= ^[a-z][0-9]?$, # Single lowercase characters, possibly followed by a single digit
195+
^[A-Z][0-9]{0,2}$, # Single uppercase characters, possibly followed by one or two digits
196+
^[A-Z]+_\d+(_dot)?$, # Uppercase characters followed by underscore and digits, optionally followed by _dot
197+
^(dry_|propellant_)[A-Z]+_\d+$, # Variables starting with 'dry_' or 'propellant_', followed by uppercase characters, underscore, and digits
198+
^[a-z]+_ISA$, # Lowercase words ending with '_ISA'
199+
^plot(1D|2D)$, # Variables starting with 'plot' followed by '1D' or '2D'
198200

199201
# Include a hint for the correct naming format with invalid-name.
200202
include-naming-hint=no

rocketpy/mathutils/function.py

Lines changed: 55 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
carefully as it may impact all the rest of the project.
55
"""
66
import warnings
7-
from inspect import signature
87
from collections.abc import Iterable
8+
from inspect import signature
99
from pathlib import Path
1010

1111
import matplotlib.pyplot as plt
@@ -222,7 +222,7 @@ def source_function(_):
222222
source = source[source[:, 0].argsort()]
223223

224224
self.x_array = source[:, 0]
225-
self.xinitial, self.xfinal = self.x_array[0], self.x_array[-1]
225+
self.x_initial, self.x_final = self.x_array[0], self.x_array[-1]
226226

227227
self.y_array = source[:, 1]
228228
self.y_initial, self.y_final = self.y_array[0], self.y_array[-1]
@@ -241,7 +241,7 @@ def source_function(_):
241241
# Do things if function is multivariate
242242
else:
243243
self.x_array = source[:, 0]
244-
self.xinitial, self.xfinal = self.x_array[0], self.x_array[-1]
244+
self.x_initial, self.x_final = self.x_array[0], self.x_array[-1]
245245

246246
self.y_array = source[:, 1]
247247
self.y_initial, self.y_final = self.y_array[0], self.y_array[-1]
@@ -251,30 +251,6 @@ def source_function(_):
251251

252252
# Finally set data source as source
253253
self.source = source
254-
255-
# Update extrapolation method
256-
if (
257-
self.__extrapolation__ is None
258-
or self.__extrapolation__ == "natural"
259-
):
260-
self.set_extrapolation("natural")
261-
else:
262-
raise ValueError(
263-
"Multidimensional datasets only support natural extrapolation."
264-
)
265-
266-
# Set default multidimensional interpolation if it hasn't
267-
if (
268-
self.__interpolation__ is None
269-
or self.__interpolation__ == "shepard"
270-
):
271-
self.set_interpolation("shepard")
272-
else:
273-
raise ValueError(
274-
"Multidimensional datasets only support shepard interpolation."
275-
)
276-
277-
# Return self
278254
return self
279255

280256
@cached_property
@@ -363,7 +339,7 @@ def set_get_value_opt(self):
363339
# Retrieve general info
364340
x_data = self.x_array
365341
y_data = self.y_array
366-
x_min, x_max = self.xinitial, self.xfinal
342+
x_min, x_max = self.x_initial, self.x_final
367343
if self.__extrapolation__ == "zero":
368344
extrapolation = 0 # Extrapolation is zero
369345
elif self.__extrapolation__ == "natural":
@@ -557,6 +533,7 @@ def set_discrete(
557533
zs = np.array(self.get_value(mesh))
558534
self.set_source(np.concatenate(([xs], [ys], [zs])).transpose())
559535
self.__interpolation__ = "shepard"
536+
self.__extrapolation__ = "natural"
560537
return self
561538

562539
def set_discrete_based_on_model(
@@ -888,7 +865,7 @@ def get_value(self, *args):
888865
x = np.array(args[0])
889866
x_data = self.x_array
890867
y_data = self.y_array
891-
x_min, x_max = self.xinitial, self.xfinal
868+
x_min, x_max = self.x_initial, self.x_final
892869
coeffs = self.__polynomial_coefficients__
893870
matrix = np.zeros((len(args[0]), coeffs.shape[0]))
894871
for i in range(coeffs.shape[0]):
@@ -909,7 +886,7 @@ def get_value(self, *args):
909886
x_data = self.x_array
910887
y_data = self.y_array
911888
x_intervals = np.searchsorted(x_data, x)
912-
x_min, x_max = self.xinitial, self.xfinal
889+
x_min, x_max = self.x_initial, self.x_final
913890
if self.__interpolation__ == "spline":
914891
coeffs = self.__spline_coefficients__
915892
for i, _ in enumerate(x):
@@ -1229,7 +1206,7 @@ def plot_1d(
12291206
else:
12301207
# Determine boundaries
12311208
x_data = self.x_array
1232-
x_min, x_max = self.xinitial, self.xfinal
1209+
x_min, x_max = self.x_initial, self.x_final
12331210
lower = x_min if lower is None else lower
12341211
upper = x_max if upper is None else upper
12351212
# Plot data points if force_data = True
@@ -1356,7 +1333,7 @@ def plot_2d(
13561333
mesh_x.shape
13571334
)
13581335
z_min, z_max = z.min(), z.max()
1359-
color_map = plt.cm.get_cmap(cmap)
1336+
color_map = plt.colormaps[cmap]
13601337
norm = plt.Normalize(z_min, z_max)
13611338

13621339
# Plot function
@@ -1530,7 +1507,7 @@ def __interpolate_polynomial__(self):
15301507
y = self.y_array
15311508
# Check if interpolation requires large numbers
15321509
if np.amax(x) ** degree > 1e308:
1533-
print(
1510+
warnings.warn(
15341511
"Polynomial interpolation of too many points can't be done."
15351512
" Once the degree is too high, numbers get too large."
15361513
" The process becomes inefficient. Using spline instead."
@@ -2648,7 +2625,7 @@ def find_input(self, val, start, tol=1e-4):
26482625
The value of the input which gives the output closest to val.
26492626
"""
26502627
return optimize.root(
2651-
lambda x: self.get_value(x) - val,
2628+
lambda x: self.get_value(x)[0] - val,
26522629
start,
26532630
tol=tol,
26542631
).x[0]
@@ -2738,10 +2715,10 @@ def compose(self, func, extrapolate=False):
27382715
if isinstance(self.source, np.ndarray) and isinstance(func.source, np.ndarray):
27392716
# Perform bounds check for composition
27402717
if not extrapolate:
2741-
if func.min < self.xinitial and func.max > self.xfinal:
2718+
if func.min < self.x_initial and func.max > self.x_final:
27422719
raise ValueError(
27432720
f"Input Function image {func.min, func.max} must be within "
2744-
f"the domain of the Function {self.xinitial, self.xfinal}."
2721+
f"the domain of the Function {self.x_initial, self.x_final}."
27452722
)
27462723

27472724
return Function(
@@ -2763,10 +2740,10 @@ def compose(self, func, extrapolate=False):
27632740
@staticmethod
27642741
def _check_user_input(
27652742
source,
2766-
inputs,
2767-
outputs,
2768-
interpolation,
2769-
extrapolation,
2743+
inputs=None,
2744+
outputs=None,
2745+
interpolation=None,
2746+
extrapolation=None,
27702747
):
27712748
"""
27722749
Validates and processes the user input parameters for creating or
@@ -2857,30 +2834,55 @@ def _check_user_input(
28572834
source_dim = source.shape[1]
28582835

28592836
# check interpolation and extrapolation
2860-
if source_dim > 2:
2837+
2838+
## single dimension
2839+
if source_dim == 2:
2840+
# possible interpolation values: llinear, polynomial, akima and spline
2841+
if interpolation is None:
2842+
interpolation = "spline"
2843+
elif interpolation.lower() not in [
2844+
"spline",
2845+
"linear",
2846+
"polynomial",
2847+
"akima",
2848+
]:
2849+
warnings.warn(
2850+
"Interpolation method for single dimensional functions was "
2851+
+ f"set to 'spline', the {interpolation} method is not supported."
2852+
)
2853+
interpolation = "spline"
2854+
2855+
# possible extrapolation values: constant, natural, zero
2856+
if extrapolation is None:
2857+
extrapolation = "constant"
2858+
elif extrapolation.lower() not in ["constant", "natural", "zero"]:
2859+
warnings.warn(
2860+
"Extrapolation method for single dimensional functions was "
2861+
+ f"set to 'constant', the {extrapolation} method is not supported."
2862+
)
2863+
extrapolation = "constant"
2864+
2865+
## multiple dimensions
2866+
elif source_dim > 2:
28612867
# check for inputs and outputs
28622868
if inputs == ["Scalar"]:
28632869
inputs = [f"Input {i+1}" for i in range(source_dim - 1)]
2864-
warnings.warn(
2865-
f"Inputs not set, defaulting to {inputs} for "
2866-
+ "multidimensional functions.",
2867-
)
28682870

28692871
if interpolation not in [None, "shepard"]:
2870-
interpolation = "shepard"
28712872
warnings.warn(
28722873
(
2873-
"Interpolation method for multidimensional functions is set"
2874-
"to 'shepard', currently other methods are not supported."
2874+
"Interpolation method for multidimensional functions was"
2875+
"set to 'shepard', other methods are not supported yet."
28752876
),
28762877
)
2878+
interpolation = "shepard"
28772879

2878-
if extrapolation is None:
2879-
extrapolation = "natural"
2880+
if extrapolation not in [None, "natural"]:
28802881
warnings.warn(
2881-
"Extrapolation not set, defaulting to 'natural' "
2882-
+ "for multidimensional functions.",
2882+
"Extrapolation method for multidimensional functions was set"
2883+
"to 'natural', other methods are not supported yet."
28832884
)
2885+
extrapolation = "natural"
28842886

28852887
# check input dimensions
28862888
in_out_dim = len(inputs) + len(outputs)
@@ -2889,21 +2891,6 @@ def _check_user_input(
28892891
"Source dimension ({source_dim}) does not match input "
28902892
+ f"and output dimension ({in_out_dim})."
28912893
)
2892-
2893-
# if function, check for inputs and outputs
2894-
if isinstance(source, Function):
2895-
# check inputs
2896-
if inputs is not None and inputs != source.get_inputs():
2897-
warnings.warn(
2898-
f"Inputs do not match source inputs, setting inputs to {inputs}.",
2899-
)
2900-
2901-
# check outputs
2902-
if outputs is not None and outputs != source.get_outputs():
2903-
warnings.warn(
2904-
f"Outputs do not match source outputs, setting outputs to {outputs}.",
2905-
)
2906-
29072894
return inputs, outputs, interpolation, extrapolation
29082895

29092896

@@ -2937,7 +2924,7 @@ def __new__(
29372924
outputs: list of strings
29382925
A list of strings that represent the outputs of the function.
29392926
interpolation: str
2940-
The type of interpolation to use. The default value is 'akima'.
2927+
The type of interpolation to use. The default value is 'spline'.
29412928
extrapolation: str
29422929
The type of extrapolation to use. The default value is None.
29432930
datapoints: int
@@ -3123,13 +3110,6 @@ def source_function(*_):
31233110

31243111
source = source_function
31253112
val = Function(source, *args, **kwargs)
3126-
except Exception as exc:
3127-
# TODO: Raising too general exception Pylint W0719
3128-
raise Exception(
3129-
"Could not create Function object from method "
3130-
f"{self.func.__name__}."
3131-
) from exc
3132-
31333113
# pylint: disable=W0201
31343114
val.__doc__ = self.__doc__
31353115
val.__cached__ = True

rocketpy/motors/liquid_motor.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
from ..mathutils.function import funcify_method, reset_funcified_methods
1+
import warnings
2+
3+
import numpy as np
4+
5+
from rocketpy.mathutils.function import (
6+
Function,
7+
funcify_method,
8+
reset_funcified_methods,
9+
)
10+
211
from ..plots.liquid_motor_plots import _LiquidMotorPlots
312
from ..prints.liquid_motor_prints import _LiquidMotorPrints
413
from .motor import Motor
@@ -264,7 +273,18 @@ def exhaust_velocity(self):
264273
mass flow rate. Therefore, this will vary with time if the mass flow
265274
rate varies with time.
266275
"""
267-
return self.thrust / (-1 * self.mass_flow_rate)
276+
times, thrusts = self.thrust.source[:, 0], self.thrust.source[:, 1]
277+
mass_flow_rates = self.mass_flow_rate(times)
278+
279+
# Compute exhaust velocity only for non-zero mass flow rates
280+
valid_indices = mass_flow_rates != 0
281+
valid_times = times[valid_indices]
282+
valid_thrusts = thrusts[valid_indices]
283+
valid_mass_flow_rates = mass_flow_rates[valid_indices]
284+
285+
ext_vel = -valid_thrusts / valid_mass_flow_rates
286+
287+
return np.column_stack([valid_times, ext_vel])
268288

269289
@funcify_method("Time (s)", "Propellant Mass (kg)")
270290
def propellant_mass(self):

rocketpy/plots/rocket_plots.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def stability_margin(self):
7676
None
7777
"""
7878

79-
self.rocket.stability_margin.plot2D(
79+
self.rocket.stability_margin.plot_2d(
8080
lower=0,
8181
upper=[2, self.rocket.motor.burn_out_time], # Mach 2 and burnout
8282
samples=[20, 20],

tests/test_flight.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,19 @@ def test_initial_solution(mock_show, example_env, calisto_robust):
147147
def test_get_solution_at_time(flight_calisto):
148148
"""Test the get_solution_at_time method of the Flight class. This test
149149
simply calls the method at the initial and final time and checks if the
150-
returned values are correct.
150+
returned values are correct. Also, checking for valid return instance.
151151
152152
Parameters
153153
----------
154154
flight_calisto : rocketpy.Flight
155155
Flight object to be tested. See the conftest.py file for more info
156156
regarding this pytest fixture.
157157
"""
158+
assert isinstance(flight_calisto.get_solution_at_time(0), np.ndarray)
159+
assert isinstance(
160+
flight_calisto.get_solution_at_time(flight_calisto.t_final), np.ndarray
161+
)
162+
158163
assert np.allclose(
159164
flight_calisto.get_solution_at_time(0),
160165
np.array([0, 0, 0, 0, 0, 0, 0, 0.99904822, -0.04361939, 0, 0, 0, 0, 0]),
@@ -576,7 +581,7 @@ def test_lat_lon_conversion_from_origin(mock_show, example_env, calisto_robust):
576581
],
577582
)
578583
def test_rail_length(calisto_robust, example_env, rail_length, out_of_rail_time):
579-
"""Test the rail length parameter of the Flight class. This test simply
584+
"""Tests the rail length parameter of the Flight class. This test simply
580585
simulate the flight using different rail lengths and checks if the expected
581586
out of rail altitude is achieved. Four different rail lengths are
582587
tested: 0.001, 1, 10, and 100000 meters. This provides a good test range.

0 commit comments

Comments
 (0)